Deadlock Prevention in Java
Deadlock is a situation in multithreading where two or more threads are waiting for each other to release resources, causing a standstill. Deadlock prevention is crucial for ensuring the smooth execution of concurrent applications.
Step 1: Understanding Deadlock
Below is an example of a potential deadlock situation:
class Resource { void method1(Resource other) { synchronized (this) { System.out.println(Thread.currentThread().getName() + " locked this resource"); synchronized (other) { System.out.println(Thread.currentThread().getName() + " locked the other resource"); } } } } public class DeadlockExample { public static void main(String[] args) { Resource r1 = new Resource(); Resource r2 = new Resource(); Thread t1 = new Thread(() -> r1.method1(r2), "Thread-1"); Thread t2 = new Thread(() -> r2.method1(r1), "Thread-2"); t1.start(); t2.start(); } }
In this example, Thread-1
locks r1
and waits for r2
, while Thread-2
locks r2
and waits for r1
. This causes a deadlock.
Step 2: Deadlock Prevention Techniques
To prevent deadlocks, you can follow these strategies:
1. Avoid Nested Locks
Try to avoid acquiring locks within locks. Below is a revised example to avoid nested locks:
class Resource { synchronized void method() { System.out.println(Thread.currentThread().getName() + " is using this resource"); } } public class AvoidNestedLocks { public static void main(String[] args) { Resource r1 = new Resource(); Resource r2 = new Resource(); Thread t1 = new Thread(() -> { synchronized (r1) { System.out.println("Thread-1 locked r1"); r1.method(); } synchronized (r2) { System.out.println("Thread-1 locked r2"); r2.method(); } }); Thread t2 = new Thread(() -> { synchronized (r2) { System.out.println("Thread-2 locked r2"); r2.method(); } synchronized (r1) { System.out.println("Thread-2 locked r1"); r1.method(); } }); t1.start(); t2.start(); } }
2. Use a Lock Ordering System
Always acquire locks in a fixed, consistent order to avoid circular dependencies:
class Resource { synchronized void use() { System.out.println(Thread.currentThread().getName() + " is using this resource"); } } public class LockOrderingExample { public static void main(String[] args) { Resource r1 = new Resource(); Resource r2 = new Resource(); Thread t1 = new Thread(() -> { synchronized (r1) { System.out.println("Thread-1 locked r1"); synchronized (r2) { System.out.println("Thread-1 locked r2"); r2.use(); } } }); Thread t2 = new Thread(() -> { synchronized (r1) { System.out.println("Thread-2 locked r1"); synchronized (r2) { System.out.println("Thread-2 locked r2"); r2.use(); } } }); t1.start(); t2.start(); } }
3. Use Try-Lock Mechanism
Java's ReentrantLock
provides a try-lock mechanism to avoid deadlocks:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Resource { private final Lock lock = new ReentrantLock(); public boolean tryLock() { return lock.tryLock(); } public void unlock() { lock.unlock(); } public void use() { System.out.println(Thread.currentThread().getName() + " is using the resource"); } } public class TryLockExample { public static void main(String[] args) { Resource r1 = new Resource(); Resource r2 = new Resource(); Thread t1 = new Thread(() -> { if (r1.tryLock()) { try { System.out.println("Thread-1 locked r1"); if (r2.tryLock()) { try { System.out.println("Thread-1 locked r2"); r2.use(); } finally { r2.unlock(); } } } finally { r1.unlock(); } } }); Thread t2 = new Thread(() -> { if (r2.tryLock()) { try { System.out.println("Thread-2 locked r2"); if (r1.tryLock()) { try { System.out.println("Thread-2 locked r1"); r1.use(); } finally { r1.unlock(); } } } finally { r2.unlock(); } } }); t1.start(); t2.start(); } }
Summary
In this tutorial, you learned:
- What deadlock is and how it occurs
- How to prevent deadlocks by avoiding nested locks
- How to implement lock ordering to avoid circular dependencies
- How to use the try-lock mechanism with
ReentrantLock
By following these techniques, you can prevent deadlocks in Java applications and ensure smooth multithreading execution.