死锁是指两个或多个线程在相互期待对方开释锁的状态,从而导致程序无奈继续执行的状况。
在Java多线程中,死锁通常是因为以下四种状况的组合所导致的:
- 互斥:多个线程竞争同一资源(如锁),每次只能有一个线程占用,其余线程必须期待。
- 占有且期待:线程在持有锁的同时,期待其余线程持有的锁。
- 不可抢占:曾经获取锁的线程不能被其余线程强制中断或开释锁。
- 循环期待:多个线程造成一个循环期待的关系,每个线程都在期待其余线程开释锁。
要解决死锁问题,能够采取以下措施:
- 防止应用多个锁:尽量应用单个锁或者应用更高级别的同步机制,比方并发汇合。
- 对立获取锁的程序:确保所有线程获取锁的程序统一,避免出现循环期待的状况。
- 尽可能放大同步代码块:缩小同步代码块的长度,放大互斥的范畴。
- 及时开释锁:尽可能早地开释锁,防止占有且期待的状况。
- 应用定时锁:在获取锁时,应用带有超时工夫的锁,防止因为期待锁而导致死锁。
- 强制中断线程:在发现死锁时,能够强制中断其中一个线程,突破循环期待的环。
须要留神的是,死锁是一个简单的问题,解决起来也比拟艰难,须要仔细分析代码和调试。
死锁代码演示:
public class DeadLock { public static void main(String[] args) { Object lockA = new Object(); Object lockB = new Object(); Thread t1 = new Thread(() -> { // 1.占有一把锁 synchronized (lockA) { System.out.println("线程1取得锁A"); // 休眠1s(让线程2有工夫先占有锁B) try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 获取线程2的锁B synchronized (lockB) { System.out.println("线程1取得锁B"); } } }); t1.start(); Thread t2 = new Thread(() -> { // 占B锁 synchronized (lockB) { System.out.println("线程2取得锁B"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 获取线程1的锁A synchronized (lockA) { System.out.println("线程2取得了锁A"); } } }); t2.start(); }}
运行后果:
这段代码是一个典型的死锁示例,它蕴含两个线程,每个线程都持有一个锁并期待另一个锁的开释,从而导致死锁。
具体来说,这里定义了两个对象锁 lockA 和 lockB,并在两个线程中别离获取这两个锁。当线程1获取锁A后,它会休眠1秒钟,而后尝试获取锁B;而当线程2获取锁A后,它也会休眠1秒钟,而后尝试获取锁B。因为两个线程都在期待对方开释锁,因而它们会始终阻塞在获取锁的代码块中,无奈继续执行,从而造成了死锁。
要解决这个问题,能够依照防止死锁的几种办法之一,例如对锁的获取程序进行对立、尽量放大同步代码块的范畴、及时开释锁等。上面是一种批改计划:
public class UnDeadLock2 { public static void main(String[] args) { Object lockA = new Object(); Object lockB = new Object(); Thread t1 = new Thread(() -> { synchronized (lockA) { System.out.println("线程1失去锁A"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB) { System.out.println("线程1失去锁B"); System.out.println("线程1开释锁B"); } System.out.println("线程1开释锁A"); } }, "线程1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (lockA) { System.out.println("线程2失去锁A"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB) { System.out.println("线程2失去锁B"); System.out.println("线程2开释锁B"); } System.out.println("线程2开释锁A"); } }, "线程2"); t2.start(); }}
在批改后的代码中,两个线程都依照雷同的程序获取锁(先获取锁A,再获取锁B),从而防止了死锁问题。此外,线程2也及时开释了锁A,防止了占有且期待的状况。
运行后果: