地 址:联系地址联系地址联系地址电 话:020-123456789网址:i.i.ii.1.twqueen.com邮 箱:admin@aa.com
欢迎来到《并发王者课》 ,王者稳本文是课黄该系列文章中的第12篇。
在上篇文章中 ,金行我们介绍了死锁的远何概念及其原因,本文将为你介绍的线程是几种常见的死锁预防策略。
简单来说 ,免于预防死锁主要有三种策略 :
通常 ,王者稳死锁的课黄产生是由于多个线程无序请求资源造成的 。资源是金行有限的 ,不可能同时满足所有线程的远何请求。然而 ,线程如果能按照一定的免于顺序分别满足各个线程的请求,那么死锁也就不再存在 ,死锁也就是王者稳所谓的顺序化加锁(Lock Ordering)。
举个通俗点的例子 ,烦人的路口堵车你一定遇到过。路口之所以堵车,是因为车太多了,大家都争相往自己的方向去,互不相让,不堵才怪。这时候 ,就需要交警在中间进行协调指挥 ,疏解拥堵。交警之所以可疏解拥堵 ,其根本原因在于 ,交警让原本处于无序竞争的车流变成了井然有序的队列。
车还是那么多的车 ,路口还是那个路口 ,可是道路通畅了,这和线程的锁竞争是类似的道理。
在上篇文章的死锁代码中,线程1和线程2分别先占有了A和B,导致了死锁 。按照刚才的思路,我们把顺序调整下,线程1和线程2都先占有A,然后再同时争夺B ,那么死锁就不会发生。
定义哪吒线程1,先抢占A再争夺B:
定义兰陵王线程2,也是先抢占A再抢占B,这与此前就不同了:
启动两个线程:
两个线程的输出结果如下:
从运行的结果中可以看到,两个线程都先后获得了自己所需要的资源,而没有导致死锁 。
调整加锁顺序是一种简单但有效的死锁预防策略。但是,这一策略并不是万能的 ,它仅适用于你在编码时已经知晓加锁的顺序 。
在上篇文章中,我们说过死锁的产生有一些必要的条件,其中一个是无限等待。设定锁超时时间正是为了打破这一条件,让无限等待变成有限等待。
仍然以前面的代码为例,哪吒和兰陵王两个线程在争夺资源时,对方都互不相让导致了无限等待的僵局 。而此时 ,如果其中任何一方给等待设定一个期限,那么时间一到 ,僵局将不攻自破 ,而线程仍可以再稍等片刻后继续尝试 。
需要注意的是,代码块不可以指定锁超时 。所以 ,如果需要锁超时,你需要使用自定义锁,或者使用JDK提供的并发工具类 。相关工具类的用法 ,会在后续文章中介绍,本文暂不展开描述。
另外,所谓给锁加一个超时的期限 ,其实有两层含义。一是在请求锁时需要设定超时时间 ,二是在获取锁之后对锁的持有也要有个超时时间,总不能到手就不放,那是耍流氓。
作为死锁预防的第三种策略 ,你可以认为死锁检测(Deadlock Detection)是一项较重的被动技能 ,当我们无法顺序化加锁,也无法设置锁的超时时间 ,那么就需要进行死锁检测。
死锁检测的核心原理在于对线程和资源进行数据化打标和跟踪 。
在线程获取锁时,会将锁和线程的对应关系通过Graph或者Map等数据结构记录下来。这样一来 ,线程在获取锁被拒绝时,可以通过遍历已经记录的数据分析是否存在死锁。
当线程发现死锁的情况后 ,可以采取释放锁 ,稍等片刻后再次尝试 。
在你感觉线程可能被阻塞或死锁时 ,可以通过命令查看 。如果存在死锁,输出的结果中会有明确的死锁提示,如下面所示:
除了之外 ,JProfiler也是一款非常强大的线程与堆栈分析工具,并可以和IDEA等IDE完美结合。
借助于JProfiler,我们可以非常直观地看到上述示例代码中的死锁,也可以在Thread Monitor中看到两个线程的状态为blocked.
需要注意的是,JProfiler是一款付费软件,它提供了十天的免费试用时间。如果没有常规的使用需求 ,而是仅用于学习的话 ,十天也是够用的 。当然,你也可以考虑使用jConsole 、jVisualvm等。
以上就是关于死锁预防策略的全部内容。在本文中,我们介绍了三种死锁预发策略。三种策略各有利弊 ,就实际工作中的应用而言 ,第二种给锁设定超时期限是更为常用的一种做法,而第一种和第三种具有一定的逻辑难度和技术难度,更侧重于理解而非实际应用。
正文到此结束,恭喜你又上了一颗星✨
夫子的试炼
延伸阅读与参考资料
关于作者
关注公众号【庸人技术笑谈】 ,获取及时文章更新 。记录平凡人的技术故事,分享有品质(尽量)的技术文章,偶尔也聊聊生活和理想。不贩卖焦虑,不做标题党 。
如果本文对你有帮助 ,欢迎点赞、关注、监督,我们一起从青铜到王者。