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.