Another situation where finally
is useful is to ensure that "manual" locks get released (locks implemented with the language's synchronizing structures internally use finally
for this purpose). When doing this, however, there is a detail which is sometimes overlooked: locks are often acquired for the purpose of allowing code to momentarily violate an object's invariants and re-establish them before anyone else can notice. If an exception occurs which would leave an object's invariants in an improper state, the proper behavior is generally not to leave the lock held, but rather ensure that a variable is set to indicate that has occurred, and have anyone that acquires the lock check this variable and throw an exception if it is set.
If one encapsulates the lock-is-valid variable within the lock object, the code might look like:
void insertItem(...)
{
try
{
myLock.acquire();
myLock.enterDanger();
...
myLock.leaveDanger();
}
finally
{
myLock.release();
}
}
Code which never modifies the guarded object but simply reads it would never have to "enterDanger". If release
gets called while the lock is still indanger
state, it may wish to capture the current stack trace; pending or future calls to myLock.acquire
should throw an exception, possibly including that stack trace as supplemental information.
Note that one could try instead to invalidate myLock
within a catch
block, but one must then ensure that nobody adds a catch
block to the try
block to handle expected exceptions without invalidating the lock. If an expected exception occurs in an unexpected place, the guarded object might be left in a corrupt state without triggering the catch
that was supposed to invalidate it (the execution of the more specific catch would prevent execution of the less-specific one).