Neeraja Gandla
Neeraja Gandla

Reputation: 117

Reentrantlock - Why do we need to acquire a lock multiple times?

I have recently been learning multithreading concepts in java. I have a few doubts which didn't resolve by looking up the relevant threads on StackOverflow. I couldn't find satisfactory answers for my following questions:

  1. wait() method makes the thread to wait till it gets the lock. Whereas, the wait(long timeout) method makes the thread wait for 'timeout' no. of milliseconds and if it still doesn't get the lock, goes back to runnable state. But to actually get to running state, it needs the lock however. So what's the point of wait(long timeout) method? The thread however releases the locks acquired by it when it's in waiting state. So the difference is not even the resources acquired by it. What difference does it make if the thread stays in waiting state or runnable state? What's the advantage of wait(long timeout) over wait() method?

  2. synchonized keyword or block provides lock on the object on which the method or block is called. It causes another thread which tries to acquire the lock on the same instance to wait. But in the case of ReentrantLock, on which object is the lock acquired? The threads trying to acquire whose lock are made to wait?

  3. How does a ReentrantLock avoid deadlock? Suppose there are two methods m1 and m2. Both need to acquire a lock. m1 is calling m2 and m2 is calling m1. How can we avoid deadlock in this situation using ReentrantLock? May be we can use tryLock() and provide an alternate operations for the thread which fails to acquire the lock. But what could be the possible alternate operations? What if the thread must need the lock to work?

  4. I found that using ReentrantLock we can acquire lock multiple times. But why do we have to acquire lock several times? I have read theoretical answers on this but couldn't really get it. It will be helpful if you can demonstrate with a clear example code.

Upvotes: 5

Views: 3956

Answers (2)

hagrawal7777
hagrawal7777

Reputation: 14658

I think few use cases of Reentrancy (why do we to acquire a lock multiple times) are discussed in some Oracle docs:

ReentrantReadWriteLock class.

This lock allows both readers and writers to reacquire read or write locks in the style of a ReentrantLock. Non-reentrant readers are not allowed until all write locks held by the writing thread have been released.

Additionally, a writer can acquire the read lock, but not vice-versa. Among other applications, reentrancy can be useful when write locks are held during calls or callbacks to methods that perform reads under read locks. If a reader tries to acquire the write lock it will never succeed.

Reentrant Synchronization

Recall that a thread cannot acquire a lock owned by another thread. But a thread can acquire a lock that it already owns. Allowing a thread to acquire the same lock more than once enables reentrant synchronization. This describes a situation where synchronized code, directly or indirectly, invokes a method that also contains synchronized code, and both sets of code use the same lock. Without reentrant synchronization, synchronized code would have to take many additional precautions to avoid having a thread cause itself to block.

Upvotes: 1

Stephen C
Stephen C

Reputation: 718798

Why do we need to acquire a lock multiple times?

Clearly, you don't need to. But it is not unusual for an application to do it "by accident". For example:

  public void binaryOperation(Operand op1, Operand op2) {
      synchronized (op1) {
          synchronized (op2) {
               // do something that needs the locks
          }
      }
  }

  // now call passing the same object for both operands
  Operand op = ...
  binaryOperation(op, op); 

In this example, the op object will actually get locked twice. If primitive locks weren't re-entrant, this could would fail (or deadlock).

Now we could fix the binaryOperation method to not do that, but it would make the code significantly more complicated.

The same scenario can happen with ReentrantLock.


Question 1.

But to actually get to running state, it needs the lock however. So what's the point of wait(long timeout) method?

This is about Object::wait. The ReentrantLock API doesn't support this. (Beware: you can use wait and notify on a ReentrantLock object, but only if you are treating it as a primitive lock. Not a good idea!)

The wait is waiting for a notification, and the timeout says how long the caller is prepared to wait for the notification. As the javadoc says:

"Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed."

With both wait() and wait(timeout), it is up to the caller to check that the condition that it was expecting to be "notified about" has actually satisfied. (See the note about "spurious wakeup" ... and the example code.)

What's the advantage of wait(long timeout) over wait() method?

Simply, it gives you the option of only waiting a limited time for the notification. If this is not useful, don't use it.


Question 2.

But in the case of ReentrantLock, on which object is the lock acquired?

Strictly speaking, it is the lock itself. What that the lock actually means will depend on how you code your classes. But that is exactly the same as with primitive mutexes.

Locking in Java doesn't prevent some misbehaving code from accessing and or updating some shared state without holding the lock. It is up to the programmer to do it correctly.

The threads trying to acquire whose lock are made to wait?

Yes.


Question 3.

How does a ReentrantLock avoid deadlock?

In general, it doesn't.

In the case of reentrant locking (i.e. in the case where a thread attempts to acquire lock A while holding lock A), the ReentrantLock implementation notices that the thread holding the lock is the thread acquiring the lock. A counter is incremented, so that the implementation knows that the lock must be released twice.

How can we avoid deadlock in this situation using ReentrantLock? May be we can use tryLock() and provide an alternate operations for the thread which fails to acquire the lock.

That is one approach.

But what could be the possible alternate operations?

  1. Ensure that all threads acquire the locks in the same order. (Deadlocks occur when threads attempt to acquire two or more threads in a different order.)

  2. If tryLock fails while holding a different lock, release the lock, wait a bit, and try again.

What if the thread must need the lock to work?

Then you design the logic so that deadlock is avoided; see the alternatives above!


Question 4.

But why do we have to acquire lock several times?

As stated above, you typically don't. But the point of a ReentrantLock is that you don't have to worry in the case where you do end up acquiring the lock twice ... for whatever reason.

Upvotes: 4

Related Questions