Search code examples
javamultithreadingscalaconcurrencyreentrantlock

Reentrantlock works fine in Java but causes IllegalMonitorException in Scala


I would like to migrate a Java function

protected static final Lock LOCK = new ReentrantLock();
public double calculate(...){
    try {
        LOCK.tryLock(20, TimeUnit.SECONDS);
        ...
    }finally{
        LOCK.unlock()
    }
}

The same function in Scala:

protected final def LOCK = new ReentrantLock
def calculate(...): double = {
    try{
        LOCK.tryLock(20, TimeUnit.Seconds)
        ...
    }finally{
        LOCK.unlock()
    }
}

The LOCK.unlock() is always causing a IllegalMonitorStateException. I dont see any reason why this is happpening.

Can someone tell me where the problem is?


Solution

  • You should definitely make LOCK a val instead of a def. As it stands, you are recreating a new instance of ReetrantLock every time you. Effectively what you are doing is this:

    try {
        // Useless as we are creating a new lock
        (new ReentrantLock).tryLock(20, TimeUnit.SECONDS).tryLock(20, TimeUnit.SECONDS).tryLock(20, TimeUnit.SECONDS);
        ...
    }finally{
        // Useless too, and will actually throw because we unlock a fresh (thus unlocked) lock
        (new ReentrantLock).unlock()
    }
    

    It is obviously bound to fail.

    You should do something like:

    object MyClass {
      private val LOCK = new ReentrantLock
    }
    class MyClass {
      def calculate(...): double = {
          try{
              LOCK.tryLock(20, TimeUnit.Seconds)
              ...
          }finally{
              LOCK.unlock()
          }
      }
    }
    

    Which is the direct translation to scala of your original java code.

    Finally, in his (now deleted) answer Jon Skeet rightly suggests:

    You should only unlock the lock if you managed to acquire it - and the conventional pattern is to put the lock/tryLock call before the try. (It doesn't matter with tryLock(), but it does matter for lock(), so we might as well be consistent.)

    Which gives:

    object MyClass {
      private val LOCK = new ReentrantLock
    }
    class MyClass {
      def calculate(...): double = {
          val gotLock = LOCK.tryLock(20, TimeUnit.Seconds)
          try {
              ...
          } finally {
            if (gotLock) {
              LOCK.unlock()
            }
          }
      }
    }