Appearance
Deadlocks
Java's thread locks are reentrant locks.
What Are Reentrant Locks?
A reentrant lock allows a thread that already holds a lock to acquire it again without causing a deadlock. Let's examine an example to understand this concept:
java
public class Counter {
private int count = 0;
public synchronized void add(int n) {
if (n < 0) {
dec(-n);
} else {
count += n;
}
}
public synchronized void dec(int n) {
count += n;
}
}
In the add()
method, once a thread enters the method, it acquires the lock on the current instance (this
). If n < 0
, the method calls dec()
, which is also synchronized and requires the same lock.
Question: Can a thread that already holds a lock acquire the same lock again?
Answer: Yes. The JVM allows a thread to repeatedly acquire the same lock it already holds. This capability makes the lock reentrant. Internally, the JVM keeps track of the number of times the lock has been acquired by the same thread. Each time the lock is acquired, the count increments by one, and each time the lock is released, the count decrements by one. The lock is only truly released when the count reaches zero.
Deadlocks
A deadlock occurs when two or more threads are blocked forever, each waiting for the other to release a lock. Consider the following scenario where a thread acquires one lock and then attempts to acquire another lock:
java
public void add(int m) {
synchronized(lockA) { // Acquire lockA
this.value += m;
synchronized(lockB) { // Acquire lockB
this.another += m;
} // Release lockB
} // Release lockA
}
public void dec(int m) {
synchronized(lockB) { // Acquire lockB
this.another -= m;
synchronized(lockA) { // Acquire lockA
this.value -= m;
} // Release lockA
} // Release lockB
}
Scenario Leading to Deadlock:
- Thread 1: Executes
add()
and acquireslockA
. - Thread 2: Executes
dec()
and acquireslockB
. - Thread 1: Attempts to acquire
lockB
but is blocked becauselockB
is held by Thread 2. - Thread 2: Attempts to acquire
lockA
but is blocked becauselockA
is held by Thread 1.
At this point, both threads are waiting indefinitely for each other to release the locks they need, resulting in a deadlock. Once a deadlock occurs, there is no mechanism to resolve it automatically, and the only solution is to forcefully terminate the JVM process.
Preventing Deadlocks
To avoid deadlocks, ensure that all threads acquire locks in a consistent order. By strictly following a predefined order when acquiring multiple locks, you eliminate the possibility of circular wait conditions. Here's how you can modify the dec()
method to prevent deadlocks by acquiring the locks in the same order as the add()
method:
java
public void dec(int m) {
synchronized(lockA) { // Acquire lockA first
this.value -= m;
synchronized(lockB) { // Then acquire lockB
this.another -= m;
} // Release lockB
} // Release lockA
}
By ensuring that both add()
and dec()
methods acquire lockA
before lockB
, you prevent the circular wait condition that leads to deadlocks.
Exercise
Observe the Deadlock Code Output and Fix It.
Summary
Reentrant Locks: Java’s
synchronized
locks are reentrant, allowing the same thread to acquire the same lock multiple times without causing a deadlock.Conditions for Deadlock: Deadlocks occur when multiple threads hold different locks and each thread attempts to acquire the lock held by another thread, leading to an infinite waiting state.
Preventing Deadlocks: Ensure that all threads acquire locks in a consistent order. By standardizing the order in which locks are obtained, you eliminate the possibility of circular wait conditions.
Impact of Deadlocks: Once a deadlock occurs, there is no built-in mechanism to resolve it, and the only recourse is to terminate the JVM process.
Best Practices:
- Consistent Lock Ordering: Always acquire multiple locks in a consistent global order across all threads.
- Minimize Lock Scope: Keep synchronized blocks as short as possible to reduce the likelihood of deadlocks.
- Use Lock Hierarchies: Design a hierarchy for lock acquisition to maintain consistency.
By adhering to these practices, you can design multithreaded Java applications that are robust against deadlocks and ensure smooth concurrent operations.