javamultithreadingthread-synchronization

Is it really necessary to put lock.unlock() in a finally block?


Is it really necessary to lock.unlock() in a finally block? The following is a common idiom

try {
    lock.lock();
    // some code
} catch (Exception e) {
    // exception handling
} finally {
    lock.unlock();
}

however, here's what the Java Language Specification has to say:

If execution of the body is ever completed, either normally or abruptly [presumably, e.g. due to an exception], an unlock action is automatically performed on that same monitor.

Is it really necessary to put lock.unlock() in a finally block then? Or did I misunderstand the specification?


Solution

  • The section of the Java Language Specification (JLS) you linked is talking about synchronized. Specifically, it's saying that if you have the following:

    synchronized (someObj) {
        // guarded code
    }
    

    Then when that synchronized block exits for any reason, including an exception being thrown from within the block, it's guaranteed the implicit monitor of someObj will be unlocked.

    The java.util.concurrent.locks API is a separate thing, and is not covered by the JLS (though obviously relies on the Java Memory Model (JMM) as defined by the JLS). That means you have to read the Javadoc of the API to know how its defined to work.

    Here's an excerpt from the package documentation:

    Interfaces and classes providing a framework for locking and waiting for conditions that is distinct from built-in synchronization and monitors [emphasis added]. The framework permits much greater flexibility in the use of locks and conditions, at the expense of more awkward syntax.

    And here's an excerpt from the documentation of Lock:

    With this increased flexibility comes additional responsibility. The absence of block-structured locking removes the automatic release of locks that occurs with synchronized methods and statements [emphasis added]. In most cases, the following idiom should be used:

    Lock l = ...;
    l.lock();
    try {
      // access the resource protected by this lock
    } finally {
      l.unlock();
    }
    

    As you can see, it's necessary to unlock the lock in a finally block1. Also note that the call to lock() is done before the try block. This prevents trying to unlock the lock in the case where the call to lock() failed for whatever reason.


    1. A major reason why the java.util.concurrent.locks API was created is to provide more flexibility than the block-structure of synchronized. You may have a more complex scenario where you need to call unlock() far removed from the call to lock() or at some unspecified moment in the future, meaning calling unlock() in a finally block may not be the correct approach. But as noted by the documentation, that means you, the developer, have more responsibility. It's up to you to guarantee unlock() is called when appropriate.