Java Examples - Deadlock and Solutions

Java 实例Java Examples

Deadlock is a situation where multiple threads are blocked simultaneously, and one or all of them are waiting for a resource to be released. Since the threads are blocked indefinitely, the program cannot terminate normally.

Four necessary conditions for deadlock in Java:

  • 1. Mutual exclusion: when a resource is used (occupied) by one thread, other threads cannot use it.
  • 2. Non-preemption: a resource requester cannot forcibly take the resource from the resource owner; the resource can only be released voluntarily by the resource owner.
  • 3. Hold and request: a resource requester keeps holding the resources it already has while requesting other resources.
  • 4. Circular wait: there exists a waiting queue: P1 occupies P2's resource, P2 occupies P3's resource, and P3 occupies P1's resource. This forms a waiting cycle.

When all four conditions above hold, a deadlock occurs. Of course, in a deadlock situation, breaking any one of the above conditions can eliminate the deadlock. The following Java code simulates the occurrence of a deadlock.

The methods to solve deadlock are: one is to use synchronized, and the other is to use the Lock explicit lock implementation.

However, if locks are used improperly and multiple objects need to be locked at the same time, a deadlock may occur, as follows:

LockTest.java file

import java.util.Date; public class LockTest { public static String obj1 = "obj1"; public static String obj2 = "obj2"; public static void main(String[] args) { LockA la = new LockA(); new Thread(la).start(); LockB lb = new LockB(); new Thread(lb).start(); } } class LockA implements Runnable{ public void run() { try { System.out.println(new Date().toString() + "LockA starts executing"); while(true){ synchronized (LockTest.obj1) { System.out.println(new Date().toString() + "LockA locks obj1"); Thread.sleep(3000); //The wait here is to give B a chance to lock synchronized (LockTest.obj2) { System.out.println(new Date().toString() + "LockA locks obj2"); Thread.sleep(60 * 1000); //For testing, once occupied, it won't be released. } } } } catch (Exception e) { e.printStackTrace(); } } } class LockB implements Runnable{ public void run() { try { System.out.println(new Date().toString() + "LockB starts executing"); while(true){ synchronized (LockTest.obj2) { System.out.println(new Date().toString() + "LockB locks obj2"); Thread.sleep(3000); //The wait here is to give A a chance to lock synchronized (LockTest.obj1) { System.out.println(new Date().toString() + "LockB locks obj1"); Thread.sleep(60 * 1000); //For testing, once occupied, it won't be released. } } } } catch (Exception e) { e.printStackTrace(); } } }

The above code produces the following output:

Tue May 05 10:51:06 CST 2015 LockB 开始执行
Tue May 05 10:51:06 CST 2015 LockA 开始执行
Tue May 05 10:51:06 CST 2015 LockB 锁住 obj2
Tue May 05 10:51:06 CST 2015 LockA 锁住 obj1

At this point, a deadlock occurs.

To solve this problem, we do not use explicit locks; we use semaphores to control.

A semaphore can control how many threads can access a resource. Here we specify that only one thread can access it, thus achieving something similar to locking. A semaphore can also specify a timeout for acquiring it, and we can perform additional processing based on this timeout.

For cases where acquisition is unsuccessful, the usual approach is to retry repeatedly, or specify a number of attempts, or exit immediately.

Let's look at the following code:

UnLockTest.java file

import java.util.Date; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class UnLockTest { public static String obj1 = "obj1"; public static final Semaphore a1 = new Semaphore(1); public static String obj2 = "obj2"; public static final Semaphore a2 = new Semaphore(1); public static void main(String[] args) { LockAa la = new LockAa(); new Thread(la).start(); LockBb lb = new LockBb(); new Thread(lb).start(); } } class LockAa implements Runnable { public void run() { try { System.out.println(new Date().toString() + "LockA starts executing"); while (true) { if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + "LockA locks obj1"); if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + "LockA locks obj2"); Thread.sleep(60 * 1000); // do something }else{ System.out.println(new Date().toString() + "LockA fails to lock obj2"); } }else{ System.out.println(new Date().toString() + "LockA fails to lock obj1"); } UnLockTest.a1.release(); //Release UnLockTest.a2.release(); Thread.sleep(1000); //Attempt immediately; in real situations, do something is uncertain. } } catch (Exception e) { e.printStackTrace(); } } } class LockBb implements Runnable { public void run() { try { System.out.println(new Date().toString() + "LockB starts executing"); while (true) { if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + "LockB locks obj2"); if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + "LockB locks obj1"); Thread.sleep(60 * 1000); // do something }else{ System.out.println(new Date().toString() + "LockB fails to lock obj1"); } }else{ System.out.println(new Date().toString() + "LockB fails to lock obj2"); } UnLockTest.a1.release(); //Release UnLockTest.a2.release(); Thread.sleep(10 * 1000); //This is only for demonstration, so tryAcquire only uses 1 second, and B must yield execution time to A; otherwise the two will always be deadlocked. } } catch (Exception e) { e.printStackTrace(); } } }

The output of the above example code is:

Tue May 05 10:59:13 CST 2015 LockA 开始执行
Tue May 05 10:59:13 CST 2015 LockB 开始执行
Tue May 05 10:59:13 CST 2015 LockB 锁住 obj2
Tue May 05 10:59:13 CST 2015 LockA 锁住 obj1
Tue May 05 10:59:14 CST 2015LockB 锁 obj1 失败
Tue May 05 10:59:14 CST 2015LockA 锁 obj2 失败
Tue May 05 10:59:15 CST 2015 LockA 锁住 obj1
Tue May 05 10:59:15 CST 2015 LockA 锁住 obj2

Java 实例Java Examples

Other Extensions