请用Java编写一段代码,要求该代码必然触发死锁,并且代码中不允许使用Thread.sleep()方法。
考察说明
考查对死锁产生四个必要条件(互斥、持有并等待、不可剥夺、循环等待)的理解,以及能否用同步机制构造必然死锁的代码。
回答思路
- 【回答框架 1】死锁产生的四个必要条件:互斥、持有并等待、不可剥夺、循环等待。代码中通过两个锁对象和两个线程,每个线程先持有自己的锁再尝试获取对方的锁,形成循环等待。
- 【回答框架 2】示例代码:定义两个对象锁lockA和lockB;线程1先同步lockA,再同步lockB;线程2先同步lockB,再同步lockA。启动两个线程后,线程1持有lockA等待lockB,线程2持有lockB等待lockA,必然互相等待,形成死锁。
- 【回答框架 3】不使用Thread.sleep()也能保证死锁:关键在于两个线程同时启动并各自获取第一个锁,然后尝试获取第二个锁。由于线程调度不确定性,可能不会同时到达,但通过让两个线程在获取第一个锁后立即尝试获取第二个锁,并配合循环重试或使用CountDownLatch等同步工具确保两个线程都持有第一个锁后再继续,可以确保必然死锁。
- 【回答框架 4】更稳妥的写法:使用两个CountDownLatch,每个线程获取第一个锁后countDown并await另一个线程的countDown,确保两个线程都持有第一个锁后再尝试获取第二个锁,从而必然死锁。
- 【回答框架 5】死锁的检测与避免:可通过jstack查看线程转储确认死锁;实际开发中应避免嵌套锁、使用锁排序或tryLock超时等策略。
- 【关键点 1】死锁四个必要条件缺一不可。
- 【关键点 2】两个线程以相反顺序获取两把锁即可构造循环等待。
- 【关键点 3】使用CountDownLatch或CyclicBarrier确保两个线程同时持有各自第一把锁,可保证必然死锁。
- 【关键点 4】不使用Thread.sleep()不影响死锁构造,关键是同步顺序。
- 【关键点 5】死锁可通过jstack检测,实际开发应避免嵌套锁或使用超时锁。
- 【易错点 1】仅启动两个线程按相反顺序加锁,可能因调度顺序未形成循环等待而不会死锁,需用同步工具保证同时持有第一把锁。
- 【易错点 2】不能使用Thread.sleep()时,不要依赖线程调度假设,应使用显式同步机制。
- 【易错点 3】死锁代码仅用于演示,实际项目中应避免,否则可能导致系统挂起。