产生背景
简单的说:线程1 想要去拿一个由 线程2 持有的锁,由于synchronized 的锁是互斥锁,某一时刻只能被一个线程所持有,所以线程1 就拿不到锁。
死锁原因 是指两个
或两个以上
的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。 原因如下:
因为系统资源不足。
进程运行推进的顺序不合适,这种产生的最多。
资源分配不当。
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,而该资源又被其他线程锁定,从而导致每一个线程都得等其它线程释放其锁定的资源,造成了所有线程都无法正常结束。
因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
产生背景: 在多线程环境下,争抢同是争抢对方资源(锁)就会产生该问题,即产生死锁。
java 死锁产生的四个必要条件
互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
举列说明场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class SyncDeadLock { private static Object locka = new Object(); private static Object lockb = new Object(); public static void main (String[] args) { new SyncDeadLock().deadLock(); } private void deadLock () { Thread thread1 = new Thread(() -> { synchronized (locka) { try { System.out.println(Thread.currentThread().getName() + " 拿到 lock-A!" ); Thread.sleep(500 ); System.out.println(Thread.currentThread().getName() + " 睡眠 500ms 后续继执行...!" ); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 偿试获取 lock-B!" ); synchronized (lockb) { System.out.println(Thread.currentThread().getName() + " 已获得 lock-B!" ); } } }, "thread1" ); Thread thread2 = new Thread(() -> { synchronized (lockb) { try { System.out.println(Thread.currentThread().getName() + " 拿到 lock-B!" ); Thread.sleep(500 ); System.out.println(Thread.currentThread().getName() + " 睡眠 500ms 后续继执行...!" ); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 偿试获取 lock-A!" ); synchronized (locka) { System.out.println(Thread.currentThread().getName() + " 已获得 lock-A!" ); } } }, "thread2" ); thread1.start(); thread2.start(); } }
结果
1 2 3 4 5 6 thread1 拿到 lock-A! thread2 拿到 lock-B! thread1 睡眠 500ms 后续继执行...! thread1 偿试获取 lock-B! thread2 睡眠 500ms 后续继执行...! thread2 偿试获取 lock-A!
帮助理解 地上放着两个桶泡面,一个老坛酸菜,一个小鸡炖磨茹。两个人一个产品,一个测试,同时出发去抢老坛酸菜和小鸡炖磨茹,产品老坛酸菜,测试拿到小鸡炖磨茹,同一时刻,产品伸要去拽测试怀里的小鸡炖磨茹,测试伸手去拽产品的老坛酸菜,两个僵持不下,就死在那了,叫死锁。如果没有开发将他们各打一顿解救出来,它们奖无法推进下去。
死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
死锁预防 如果只使用一个锁就不会有死锁的问题,不过复杂场景下不太理实。
以确定的顺序获得锁 1 2 3 线程A ---> 锁定 A ----> 偿试锁定 B 线程B ---> 锁定 A ----> 偿试锁定 B 这样就不会发生死锁
超时放弃 Lock 接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException
方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。
信号量控制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import java.util.Date;import java.util.concurrent.Semaphore;import java.util.concurrent.TimeUnit;public class UnLockTest { public static final Semaphore a1 = new Semaphore(1 ); 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 开始执行" ); while (true ) { if (UnLockTest.a1.tryAcquire(1 , TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockA 锁住 obj1" ); if (UnLockTest.a2.tryAcquire(1 , TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockA 锁住 obj2" ); Thread.sleep(60 * 1000 ); }else { System.out.println(new Date().toString() + "LockA 锁 obj2 失败" ); } }else { System.out.println(new Date().toString() + "LockA 锁 obj1 失败" ); } UnLockTest.a1.release(); UnLockTest.a2.release(); Thread.sleep(1000 ); } } catch (Exception e) { e.printStackTrace(); } } } class LockBb implements Runnable { public void run () { try { System.out.println(new Date().toString() + " LockB 开始执行" ); while (true ) { if (UnLockTest.a2.tryAcquire(1 , TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockB 锁住 obj2" ); if (UnLockTest.a1.tryAcquire(1 , TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockB 锁住 obj1" ); Thread.sleep(60 * 1000 ); }else { System.out.println(new Date().toString() + "LockB 锁 obj1 失败" ); } }else { System.out.println(new Date().toString() + "LockB 锁 obj2 失败" ); } UnLockTest.a1.release(); UnLockTest.a2.release(); Thread.sleep(10 * 1000 ); } } catch (Exception e) { e.printStackTrace(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Sun Oct 10 12:15:25 CST 2018 LockA 锁住 obj1 Sun Oct 10 12:15:25 CST 2018 LockA 锁住 obj2 Sun Oct 10 12:15:43 CST 2018 LockB 锁住 obj2 Sun Oct 10 12:15:43 CST 2018 LockB 锁住 obj1 Sun Oct 10 12:16:26 CST 2018 LockA 锁住 obj1 Sun Oct 10 12:16:26 CST 2018 LockA 锁住 obj2 Sun Oct 10 12:16:53 CST 2018 LockB 锁住 obj2 Sun Oct 10 12:16:53 CST 2018 LockB 锁住 obj1 Sun Oct 10 12:17:27 CST 2018 LockA 锁住 obj1 Sun Oct 10 12:17:27 CST 2018 LockA 锁住 obj2 Sun Oct 10 12:18:03 CST 2018 LockB 锁住 obj2 Sun Oct 10 12:18:03 CST 2018 LockB 锁住 obj1 Sun Oct 10 12:18:28 CST 2018 LockA 锁住 obj1 Sun Oct 10 12:18:28 CST 2018 LockA 锁住 obj2 Sun Oct 10 12:19:13 CST 2018 LockB 锁住 obj2 Sun Oct 10 12:19:13 CST 2018 LockB 锁住 obj1