Can Locks (java.util.concurrent.locks.Lock) be garbage collected while locked? Suppose a purely theoretical example:
WeakReference r;
public void foo(){
Lock lock = new ReentrantLock();
r = new WeakReference(lock);
lock.lock();
}
Could lock
be garbage collected after foo()
gets executed? In other words, does lock.lock()
create any strong references back to the lock?
How do you know?
The LOCK THREAD statement ensures that no other thread executes. This condition remains in effect until the thread is unlocked (with UNLOCK THREAD) or the thread terminates.
Locks are a very important feature that make multithreading possible. Locks are a synchronization technique used to limit access to a resource in an environment where there are many threads of execution.
For a thread to work on an object, it must have control over the lock associated with it, it must “hold” the lock. Only one thread can hold a lock at a time. If a thread tries to take a lock that is already held by another thread, then it must wait until the lock is released.
A locked Lock
can be garbage collected when it is no longer reachable. (The definition of "reachable" in the JLS is: "A reachable object is any object that can be accessed in any potential continuing computation from any live thread." - JLS 12.16.1)
However, a locked Lock
that some thread is waiting on must be executing one of the Lock's lock / tryLock instance methods. For this to happen, the thread must have a reference to the lock; i.e. one that the lock method is currently accessing. Therefore, a locked Lock that some thread is trying to acquire is reachable, and cannot be garbage collected.
In other words, does lock.lock() create any strong references back to the lock?
No. In your example, the strong reference exists in the form of the lock
variable. But suppose that we tweaked your example to get rid of lock
; e.g.
public void do_lock(WeakReference<ReentrantLock> r)
r.get().lock();
}
When you call get()
, it will return a reference to the ReentrantLock
object which will be held in a temporary variable or register, making it strongly reachable. It will continue to be strongly reachable as long as the lock()
call is running. When the lock()
call returns, the ReentrantLock
object may become weakly reachable (again).
How do you know?
How do I know? A combination of:
There is not need to implement Lock
using global queues, and hence no reason to have a hidden reference to the Lock
object that would prevent it becoming unreachable. Furthermore, a Lock
that could not be garbage collected when it was locked would be a storage leak, and a major implementation flaw, and I cannot imagine Doug Lea et al making that mistake!
It turns out that while we often conceptually think that threads 'obtain' and 'own' locks, it's actually not the case from the implementation perspective. The locks keep references to owning and waiting threads, while the threads have no references to locks, and have no knowledge of the locks they 'own'.
The ReentrantLock implementation is also rather straightforward: there are no static collections of locks, and there are no background maintenance threads that keep track of locks.
Neither creating, nor locking a lock creates any 'hidden' new strong references anywhere, so, in the example above, the lock
can be garbage collected once foo()
is done.
One can verify this by perusing the source code:
http://fuseyism.com/classpath/doc/java/util/concurrent/locks/ReentrantLock-source.html
http://fuseyism.com/classpath/doc/java/util/concurrent/locks/AbstractQueuedSynchronizer-source.html
http://fuseyism.com/classpath/doc/java/util/concurrent/locks/AbstractOwnableSynchronizer-source.html
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With