Once the concurrent program is deadlocked, we can only restart the application. The best way to solve the deadlock problem is to avoid deadlock.
Conditions for deadlock
- Mutually exclusive, shared resources can only be occupied by one thread
- Hold and wait. Thread t1 has acquired shared resource s1. When trying to acquire shared resource s2, shared resource s1 is not released.
- Cannot preempt. Other threads cannot forcibly preempt the resource s1 occupied by thread t1.
- Loop wait, thread t1 waits for resources occupied by thread t2, thread t2 waits for resources occupied by thread t1
How to avoid deadlock
For the above four conditions, as long as one of them is broken, deadlock can be avoided.
The first condition "mutual exclusion" cannot be broken, because the purpose of locking is to ensure mutual exclusion.
Three other conditions, we can try
- Apply for all resources at one time, and destroy the "hold and wait" condition
- When the thread occupying part of resources further applies for other resources, if the application fails, it will actively release the resources it occupies and destroy the "non preemptive" condition.
- Apply for resources in sequence, and destroy the "circular waiting" condition
Use the management class to apply for all resources at one time and destroy the "occupy and wait" condition example
package constxiong.concurrency.a023; import java.util.HashSet; import java.util.Set; /** * Test one-time application for all resources, destroy the "occupy and wait" condition example * @author ConstXiong * @date 2019-09-24 14:04:12 */ public class TestBreakLockAndWait { //Single instance resource management class private final static Manger manager = new Manger(); //Resources 1 private static Object res1 = new Object(); //Resources 2 private static Object res2 = new Object(); public static void main(String[] args) { new Thread(() -> { boolean applySuccess = false; while (!applySuccess) { //Apply to management class for res1 and res2, failed to apply, try again applySuccess = manager.applyResources(res1, res2); if (applySuccess) { try { System.out.println("Threading:" + Thread.currentThread().getName() + " Apply res1,res2 Resource success"); synchronized (res1) { System.out.println("Threading:" + Thread.currentThread().getName() + " Get to res1 Resource locks"); //Dormancy for one second try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } synchronized (res2) { System.out.println("Threading:" + Thread.currentThread().getName() + " Get to res2 Resource locks"); } } } finally { manager.returnResources(res1, res2);//Restitution resources } } else { System.out.println("Threading:" + Thread.currentThread().getName() + " Apply res1,res2 Resource failure"); //Failed to apply to sleep for 200ms and then try again try { Thread.sleep(200); } catch (Exception e) { e.printStackTrace(); } } } }).start(); new Thread(() -> { boolean applySuccess = false; while (!applySuccess) { //Apply to management class for res1 and res2, failed to apply, try again applySuccess = manager.applyResources(res1, res2); if (applySuccess) { try { System.out.println("Threading:" + Thread.currentThread().getName() + " Apply res1,res2 Resource success"); synchronized (res2) { System.out.println("Threading:" + Thread.currentThread().getName() + " Get to res1 Resource locks"); //Dormancy for one second try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } synchronized (res1) { System.out.println("Threading:" + Thread.currentThread().getName() + " Get to res2 Resource locks"); } } } finally { manager.returnResources(res1, res2);//Restitution resources } } else { System.out.println("Threading:" + Thread.currentThread().getName() + " Apply res1,res2 Resource failure"); //Failed to apply to sleep for 200ms and then try again try { Thread.sleep(200); } catch (Exception e) { e.printStackTrace(); } } } }).start(); } } /** * Resource application and return management * @author ConstXiong * @date 2019-09-24 14:10:57 */ class Manger { //Resource storage collection private Set<Object> resources = new HashSet<Object>(); /** * Application resources * @param res1 * @param res2 * @return */ synchronized boolean applyResources(Object res1, Object res2) { if (resources.contains(res1) || resources.contains(res1)) { return false; } else { resources.add(res1); resources.add(res2); return true; } } /** * Restitution resources * @param res1 * @param res2 */ synchronized void returnResources(Object res1, Object res2) { resources.remove(res1); resources.remove(res2); } }
The printing result is as follows: thread-1 can successfully apply for res1 and res2 locks only after thread-0 releases resources.
Thread: Thread-0 successfully applied for res1 and res2 resources Thread: Thread-0 obtains the lock of res1 resource Thread: Thread-1 failed to apply for res1 and res2 resources Thread: Thread-1 failed to apply for res1 and res2 resources Thread: Thread-1 failed to apply for res1 and res2 resources Thread: Thread-1 failed to apply for res1 and res2 resources Thread: Thread-1 failed to apply for res1 and res2 resources Thread: Thread-0 obtains the lock of res2 resource Thread: Thread-1 failed to apply for res1 and res2 resources Thread: Thread-1 successfully applied for res1 and res2 resources Thread: Thread-1 obtains the lock of res1 resource Thread: Thread-1 obtains the lock of res2 resource
Use tryLock() method of Lock to release all resources due to Lock acquisition failure. Destroy the "non preemptive" condition example
package constxiong.concurrency.a023; import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * When the thread that occupies part of the resources further applies for other resources, if the application fails, it will actively release the resources it occupies and destroy the "non preemptive" condition. * @author ConstXiong * @date 2019-09-24 14:50:51 */ public class TestBreakLockOccupation { private static Random r = new Random(); private static Lock lock1 = new ReentrantLock(); private static Lock lock2 = new ReentrantLock(); public static void main(String[] args) { new Thread(() -> { //Identify whether the task is completed boolean taskComplete = false; while (!taskComplete) { lock1.lock(); System.out.println("Threading:" + Thread.currentThread().getName() + " Get lock lock1 Success"); try { //Sleep at random to help create a deadlock environment try { Thread.sleep(r.nextInt(30)); } catch (Exception e) { e.printStackTrace(); } //Thread 0 tried to get lock2 if (lock2.tryLock()) { System.out.println("Threading:" + Thread.currentThread().getName() + " Get lock lock2 Success"); try { taskComplete = true; } finally { lock2.unlock(); } } else { System.out.println("Threading:" + Thread.currentThread().getName() + " Get lock lock2 fail"); } } finally { lock1.unlock(); } //Sleep randomly to avoid live lock try { Thread.sleep(r.nextInt(10)); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(() -> { //Identify whether the task is completed boolean taskComplete = false; while (!taskComplete) { lock2.lock(); System.out.println("Threading:" + Thread.currentThread().getName() + " Get lock lock2 Success"); try { //Sleep at random to help create a deadlock environment try { Thread.sleep(r.nextInt(30)); } catch (Exception e) { e.printStackTrace(); } //Thread 2 attempts to acquire lock lock1 if (lock1.tryLock()) { System.out.println("Threading:" + Thread.currentThread().getName() + " Get lock lock1 Success"); try { taskComplete = true; } finally { lock1.unlock(); } } else { System.out.println("Threading:" + Thread.currentThread().getName() + " Get lock lock1 fail"); } } finally { lock2.unlock(); } //Sleep randomly to avoid live lock try { Thread.sleep(r.nextInt(10)); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
The results are as follows
Thread: Thread-0 obtains lock lock1 successfully Thread: Thread-1 obtains lock2 successfully Thread: Thread-1 failed to get lock lock1 Thread: Thread-1 obtains lock2 successfully Thread: Thread-0 failed to get lock lock2 Thread: Thread-1 obtains lock lock1 successfully Thread: Thread-0 obtains lock lock1 successfully Thread: Thread-0 obtains lock lock2 successfully
Lock in a certain order to break the "cycle waiting" condition example
package constxiong.concurrency.a023; /** * Test to apply resources in sequence, and destroy the "cycle waiting" condition * @author ConstXiong * @date 2019-09-24 15:26:23 */ public class TestBreakLockCircleWait { private static Object res1 = new Object(); private static Object res2 = new Object(); public static void main(String[] args) { new Thread(() -> { Object first = res1; Object second = res2; //Compare the hashcodes of res1 and res2. If the hashCode of res1 is > res2, exchange first and second. Ensure that objects with small hashCode are locked first if (res1.hashCode() > res2.hashCode()) { first = res2; second = res1; } synchronized (first) { System.out.println("Threading:" + Thread.currentThread().getName() + "Access to resources " + first + " Lock success"); try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } synchronized(second) { System.out.println("Threading:" + Thread.currentThread().getName() + "Access to resources " + second + " Lock success"); } } }).start(); new Thread(() -> { Object first = res1; Object second = res2; //Compare the hashcodes of res1 and res2. If the hashCode of res1 is > res2, exchange first and second. Ensure that objects with small hashCode are locked first if (res1.hashCode() > res2.hashCode()) { first = res2; second = res1; } synchronized (first) { System.out.println("Threading:" + Thread.currentThread().getName() + "Access to resources " + first + " Lock success"); try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } synchronized(second) { System.out.println("Threading:" + Thread.currentThread().getName() + "Access to resources " + second + " Lock success"); } } }).start(); } }
The results are as follows
Thread: Thread-0 obtains the resource java.lang.Object@7447157c lock successfully Thread: Thread-0 obtains the resource java.lang.Object@7a80f45c lock successfully Thread: Thread-1 obtains the resource java.lang.Object@7447157c lock successfully Thread: Thread-1 obtains the resource java.lang.Object@7a80f45c lock successfully
Java interview question summary, there is always a stuck you!