死锁(Dead Lock)是指两个或多个计算单元(进程、线程或协程),都在等待对方停止执行以获取系统资源,但没有一个提前退出,这就称为僵局。
死锁示例代码如下:
公共 类 DeadLock示例 { 公共 静态 空 main(string [] args){object locka =new对象(); //创建锁A对象lockB = newObject(); //创建锁B //创建线程1线程t1 = newThread(new 可运行() { @Override 公共 void 运行() { 同步(锁A) { System.out.println("线程 1: 获取锁 A!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println (》主题1:等待获取B。 ..");同步 (lockB) { System.out.println("线程 1: 获取锁 B!"); } } }}) ; t1.start(); // 运行线程 // 创建线程 2 线程 t2 = new 线程(new 可运行() { @Override 公共 void 运行() { 同步(锁B) { System.out.println("线程 2: 已获取锁 B!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println (“主题2:等待获得A。 .."); synchronized (lockA) { System.out.println("线程 2 : 获取锁 A !”); } } } }); t2.start(); //运行线程 }}
上述程序的执行结果如下:
从上面的结果可以看出,线程1和线程2都进入了死锁状态,各自在等待对方释放锁。
从上面的例子分析,可以得出产生死锁需要满足以下4个条件:
只有这四个条件同时满足,才会出现死锁问题。
也就是说,要想造成死锁,必须同时满足以上四个条件。那么我们可以通过破坏任意一个条件来解决死锁问题。
接下来我们来分析一下导致死锁的四种情况。其中哪些可以被摧毁?哪些是不能被破坏的?
通过上面的分析,我们可以得出结论,只能通过破坏请求和保持条件或者循环等待条件来解决死锁问题。上线的时候,我们首先会从破坏“循环等待条件”开始解决死锁问题。
所谓顺序锁是指为了避免循环等待情况,解决死锁问题而获取锁。
当我们不使用顺序锁时,程序的执行可能是这样的:
线程1先获取锁A,然后获取锁B。线程2和线程1同时执行。线程2先获取锁B,再获取锁A,这样双方先占用自己的资源(锁A和锁A)。 B) 之后尝试获取对方的锁,导致出现循环等待问题,最终出现死锁问题。
这时候我们只需要统一线程1和线程2获取锁的顺序即可。即线程1和线程2同时执行后,都先获取锁A,再获取锁B。执行过程如下图所示:
因为只有一个线程可以成功获取锁A,所以没有获取锁A的线程会先等待获取锁A。此时获取锁A的线程继续获取锁B,因为没有线程竞争并拥有锁B,那么获取锁A的线程就会成功拥有锁B,然后执行相应的代码释放锁所有的锁资源。那么另一个等待获取锁A的线程就可以成功获取锁资源并执行后续代码,所以不会出现死锁问题。
顺序锁的实现代码如下:
public class 解决DeadLock示例 { 公共 静态 空 main(String[] args) { 对象 lockA = new 对象( ); //创建锁A对象lockB = newObject(); //创建锁B //创建线程1 线程t1 = new线程() 新可运行(){@Override public void 运行() { 同步(锁A ) { System.out.println("线程 1: 获取锁 A!"); try { Thread.sleep(1000) ; } catch (InterruptedException e) { e.printStackTrace(); } System.out.print ln("线程 1:等待获取 B ...");同步zed (lockB) { System.out.println("线程 1: 获取锁 B!"); } } } } ); t1.start(); // 运行线程 // 创建线程 2 线程 t2 = new 线程() 新可运行(){ @Overridepublicvoidrun() { 同步(锁A){ System.out.println("线程 2: 已获取锁 A!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( “主题2:等待获得B.. 。!"); } } } }); t2.start(); // 运行线程 }}
从上面的执行结果可以看出,程序不存在死锁问题。
轮询锁通过打破“请求和保持条件”来避免死锁。它的实现思路简单来说就是尝试通过轮询的方式获取锁。如果其中一个锁获取失败,则释放当前线程拥有的锁。所有锁并等待下一轮再尝试获取锁。
轮询锁的实现需要使用ReentrantLock的tryLock方法。具体实现代码如下:
导入java.util.concurrent.locks.Lock;导入java.util.concurrent.locks.ReentrantLock;public 类 解决DeadLock示例 { public 静态 空隙 主( String[] args) { 锁 lockA = new ReentrantLock(); //创建锁A 锁lockB = newReentrantLock(); // 创建锁 B // 创建线程 1(使用轮询锁) 线程 t1 = new Thread( 新可运行(){ @Override 公共 void run() { // 调用轮询锁 pollingLock(lockA, lockB); } }); t1.start(); // 运行线程 // 创建线程 2 线程 t2 = new Thread(new 可运行() { @Override 公共 void 运行(){lockB.lock(); // 锁定 System.out.println("线程 2: 获得锁 B!"); try { Thread.sleep( 1000); System.out .println("线程 2: 等待获取 A ..."); lockA.lock(); //锁定 try { System.out.println("线程2:获取锁A!" ); } 最后 {lockA.unlock(); //释放锁 } } catch (InterruptedException e) { e.printStackTrace(); } 终于 {lockB.unlock(); //释放锁定 } } }); t2.start(); // 运行线程 } /** * 轮询锁定 */ 公共 static void pollingLock(锁定lockA,锁定lockB) { 同时 (true) ) { if (lockA.tryLock()) { //尝试获取锁 System.out.println() ”主题 1:获得锁A !"); try {Thread.sleep(1000); System.out .println("线程 1:等待获得B. .."); if (lockB.tryLock()) { //尝试获取锁 尝试 { 系统。 out.println(“线程1:获得的锁B!”);}finally{lockb.unlock(); //释放锁 System.out.println("线程 1: 释放锁 B."); break ; } } } catch(InterruptedException e){ e.printStackTrace(); } 最后 { lockA.unlock(); //释放锁 System.out.println("线程1:释放锁A."); } }//等待一秒再继续执行 try { Thread .sleep(1000); } catch(InterruptedException e){ e.printStackTrace(); } } }}
总结 本文介绍了2种死锁的解决方案: 第一种顺序锁:通过改变获取锁的顺序,打破“循环请求条件”,避免死锁问题; 第二种轮询锁:通过轮询打破“请求和自身条件”,解决死锁问题。它的实现思路是尝试通过自旋来获取锁。在获取锁的途中,如果任意一个锁获取失败,那么之前获取到的所有锁都会被释放,一段时间后,会再次执行之前的流程,这样就避免了一个锁被(a)占用的尴尬情况。线程),从而避免了死锁问题。 参考文献和致谢 《Java并发编程实战》 推荐Java面试问题指南 技术卷入组,一起学习吧! ! PS:因为公众号平台更改了推送规则,如果不想错过内容,看完记得点击“在看”,加个” star”,这样每次推送新文章都会立即出现在你的订阅列表中。 点击“观看”支持我们!
本文介绍了2种死锁的解决方案:
第一种顺序锁:通过改变获取锁的顺序,打破“循环请求条件”,避免死锁问题;
第二种轮询锁:通过轮询打破“请求和自身条件”,解决死锁问题。它的实现思路是尝试通过自旋来获取锁。在获取锁的途中,如果任意一个锁获取失败,那么之前获取到的所有锁都会被释放,一段时间后,会再次执行之前的流程,这样就避免了一个锁被(a)占用的尴尬情况。线程),从而避免了死锁问题。
推荐
Java面试问题指南
技术卷入组,一起学习吧! !
PS:因为公众号平台更改了推送规则,如果不想错过内容,看完记得点击“在看”,加个” star”,这样每次推送新文章都会立即出现在你的订阅列表中。 点击“观看”支持我们!