Reputation:
In Java Concurrency In Practice I came the technique to avoid deadlock. He proposed to acquire use tryLock()
when we need to acquire multiple locks to ensure consistency. As follows:
public void m(MyObject o1, MyObject o2){
synchronized(o1){
synchornized(o2){
//...
}
}
}
Instead we'd better of using this:
public void m(MyObject o1, MyObject o2){
while(true){
if(o1.lock.tryLock(){
try{
if(o2.lock.tryLock(){
try{
//...
} finally {
o2.lock.unlock();
}
}
} finally {
o2.lock.unlock()
}
}
}
}
Now he goes:
This technique works only when the two locks are acquired together; if multiple locks are acquired due to nesting of method calls you cannot just realease the outer lock, even if you know you hold it.
It's not quite obvious. Why cannot we use this when calling a method within a method? Can you give an example?
Upvotes: 2
Views: 79
Reputation: 16367
I've just looked into the book and found that the quoted statement was made in the context of timed locks (using tryLock(long time, TimeUnit unit)
), but I think it also applies for your tryLock()
example.
Theoretically you can still use this technique with nested method calls, but it gets unwieldy really fast. Let's consider a simple example where you try to obtain some lock in the method foo(Lock lock)
and if you succeed, a couple of stack frames later you try to obtain another lock in the method bar(Lock lock)
.
while (true)
until they finally manage to obtain their lock, you can easily get into a deadlock if one thread has obtained A
in foo()
, another has obtained B
in foo()
and now both are spinning endlessly in bar()
, trying to acquire each other's locks.foo()
method and return from bar()
when the lock acquisition is unsuccessful, you may be able to avoid a deadlock, but now:
foo()
and bar()
are tightly coupledbar()
is called, the vulnerability window between the acquisition of the first lock and the acquisition of the second lock can be very bigfoo()
InterruptedException
, and you'll need to decide how you'd want to loop.
IllegalMonitorStateException
s, which is not too niceIf you are regularly acquiring the same sub-set of locks and using the same sub-set of resources, you may want to consider lock coarsening (combining a couple of your locks in one) or redefining your resource protection policies so that you don't need to obtain multiple levels of locks a in a single workflow.
P.S. After a couple of attempts, I got both the tryLock()
and the timed tryLock(long time, TimeUnit unit)
approaches to work with the above two-method, two-lock scheme. Not pretty, and I could easily see how a simple change in the protected section would break the whole scheme or make it just too complex.
Upvotes: 2