Shirgill Farhan Ansari Shirgill Farhan Ansari - 4 months ago 7
Java Question

What happens if a thread tries to acquire a lock that it already holds?

This is taken from a book written by Joshua Bloch.

I am not a native English Speaker, hence the reason to ask for clarification of the doubt.


Because intrinsic locks are reentrant, if a thread tries
to acquire a lock that it already holds, the request
succeeds. Reentrancy means that locks are acquired on a
per-thread rather than **per-invocation basis.


By per-invocation basis, does he means on a per method call?
Consider the snippet:

class Factoriser{

public synchronized void doSomething(){
// code goes here
}
}


Suppose there is a Thread A and is able to acquire a lock on object having the instance method doSomething(). Due to some reason the same thread Thread A again acquires a lock on the same object instance method doSomething()(imagine also that the previous lock has not been released yet).

If I understand the Joshua's statement correctly, then there will be only a single lock even though there are 2 method call/ invocations. Is my understanding 100% correct. Please exemplify. I am confused because the author clarifies this in the below paragraph which further perplexed me.


Reentrancy is implemented by associating with each lock an acquisition count and an owning thread. When the count is zero, the lock is considered unheld. When a thread acquires a previously unheld lock, the JVM records the owner
and sets the acquisition count to one. If that same thread
acquires the lock again, the count is incremented, and
when the owning thread exits the synchronized block, the
count is decremented. When the count reaches zero, the lock is released.


If the Reentrancy/locks acquiring is not on a per-invocation basis, why is the count done by JVM set to 2 for the scenario described above by me?

Answer

The counter is used to match lock acquirings and droppings. The lock can be released only when counter is 0. Marking the method foo() as synchronized and invoking it on the object obj is identical to the following block:

// calling obj.foo()
synchronized(obj) {
    // Do the foo-work
}

Let's assume we have two synchronized methods: foo() and bar() and the latter is called from the former. Invocations will have the following structure:

final FooBar obj = new FooBar();

// calling obj.foo()
synchronized(obj) { // 1. here the lock is acquired, the counter is set to 1

    // do some foo-work

    // calling obj.bar()
    synchronized(obj) {  // 2. the same lock object, the counter is set to 2
        // do the bar-work
    } // 3. the counter is set to 1, the lock is still not released

    // continue doing the foo-work
} // 4. the counter is 0, the lock is released

Without the counter, on step 3 we would release the lock which would be an error because we are still in the outer synchronized block. So, the counter is needed to correctly implement reentrance.