多线程之探讨死锁的成因和解决方案

简介: 多线程之探讨死锁的成因和解决方案

        目录:

                                        🌸1.什么是死锁

                                     🌸 2.死锁的情况

 🌸3.死锁的条件

                            🌸4.死锁的解决办法

1.什么是死锁呢?


死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线 程被无限期地阻塞,因此程序不可能正常终止.用更加通俗的说法来说,就是锁解不开了.

2.死锁的情况


1.不可重入锁的死锁

2.两个线程两把锁的死锁情况

3.多个线程多把锁的死锁情况(哲学家吃饭问题)

下面来具体分析

1.不可重入锁的死锁


一个线程一把锁,同时对该对象卡卡加锁两次,产生死锁,这就叫做不可重入锁,产生死锁情况

举个很通俗的例子:


小明从外地旅游回来,想要开门,结果才意识到家门钥匙放到了车里,想打开车门取家门钥匙,可是不幸的是车钥匙放到家里了,这个场景就是死锁,没有人可以解开锁


写个伪代码


1synchronized(locker){
2synchronized(locker){
}
}


这就是一个一个线程,同时对同一个对象locker加锁,产生死锁,为了方便讲解,我们讲它俩进行编号,现在对1号加锁,我想对2号加锁,那么就不能,因为:要想对2号加锁,得先对1号解锁,要想对1号解锁,要先把2号的锁给解了,这段逻辑是有矛盾的,所以就造成了死锁的情况


2.两个线程两把锁的死锁情况


举个通俗的例子


大妹和二妹要吃火锅,一盘毛肚在大妹边上,一盘小酥肉在二妹边上,大妹拿起毛肚,二妹拿起小酥肉,大妹说:我想吃小酥肉,你把小酥肉给我我就给你毛肚


二妹说:我想吃毛肚,你把毛肚给我我就给你小酥肉


现在的局面就是僵在这儿了,谁也不让谁


这就造成了死锁的局面


写成伪代码就是


A synchronized(locker1){
               synchronized(locker2){
}
 }                                                                                     
B synchronized(locker2){
               synchronized(locker1){
}
 }                 


上面就是两个线程两把锁,我对A的1加锁,对B的2加锁,此时在A中想对2加锁,在B中想对1加锁,这是做不到的,因此,也就造成了死锁

3.多个线程多把锁(哲学家吃饭问题)


b8a7d4ca53044ff09988d6f411590797.png

是这样的场景:


五位哲学家围坐在一张桌子上吃面条,但是只有五根筷子,五位科学家同时拿起筷子,要吃面条,当同时拿起两根筷子开始吃,吃完放下筷子,在不吃的时候就思考问题,哲学家都很固执,如果他想拿筷子,但是被别人拿走了就会等待,也不愿意放下自己已经拿到的筷子.


现在每个人同时拿起左手边的筷子,因为固执,所以谁都不愿意放下自己的筷子,那么现在就造成了死锁,就僵在这里了


3.死锁的必要条件


说了这么多,现在来总结一下死锁的必要条件:

1.互斥使用.一个线程拿到一把锁之后,另一个线程不能再拿到了,这个也是锁的基本特点

2.不可抢占.一个线程拿到锁,其他的线程是获取不到锁的,只能等这个锁被主动释放,不能被其他线程强行占有

3.请求和保持.类似于上面的哲学家问题,有了一根筷子,还要第二根筷子,但是同时不想放弃手里已有的筷子

4.循环等待.(类似于上图的哲学家问题)

1号等2号,2号等3号,3号等4号,4号等5号......这样进行循环等待


4.解决死锁问题

  那么针对死锁我们最快的解决办法就是针对第四个条件,我们要破除循环等待,具体做法是啥呢

那就是针对锁排序,如果要同时获取多把锁,约定加锁顺序,先对序号小的加锁,再对大的加锁,约定,先获取小编号的锁,再获取大编号的锁


91cb7468466148d0b35ddb6474f6bd11.png


过程是这样的,首先 我们让每个哲学家拿起号比较小的筷子,先让2号拿筷子,那肯定拿1号筷子,3号拿2号筷子,以此类推

这样1号就没有筷子了,那么5号就先吃面条了,5号拿起5号筷子,吃完放下筷子,4号拿起4号筷子,吃上面条,也放下筷子,3号,2号以此类推,最后1号吃面条.

因此,要解决死锁问题,我们只要注意加锁顺序就行了,先对小编号的加锁,再对大编号的加锁.


A synchronized(locker1){
               synchronized(locker2){
}
 }                                                                                     
B synchronized(locker1){
               synchronized(locker2){
}
 }                 


经过修改,发现不死锁了,为啥呢.因为:现在A中对1加锁,B想对1加锁,那么B就得阻塞等待,所以就得等到A执行完释放锁B才能执行

所以,我们平时在写代码的时候一定要注意加锁顺序!!!

相关文章
|
1月前
|
JavaScript 前端开发 安全
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
41 0
|
1月前
|
安全 Java 程序员
【多线程-从零开始-肆】线程安全、加锁和死锁
【多线程-从零开始-肆】线程安全、加锁和死锁
44 0
|
3月前
|
安全 算法 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(下)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
79 6
|
3月前
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(中)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
88 5
|
3月前
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(上)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
87 3
|
3月前
|
Java
Java多线程-死锁的出现和解决
死锁是指多线程程序中,两个或以上的线程在运行时因争夺资源而造成的一种僵局。每个线程都在等待其中一个线程释放资源,但由于所有线程都被阻塞,故无法继续执行,导致程序停滞。例如,两个线程各持有一把钥匙(资源),却都需要对方的钥匙才能继续,结果双方都无法前进。这种情况常因不当使用`synchronized`关键字引起,该关键字用于同步线程对特定对象的访问,确保同一时刻只有一个线程可执行特定代码块。要避免死锁,需确保不同时满足互斥、不剥夺、请求保持及循环等待四个条件。
|
3月前
|
Java 测试技术 PHP
父子任务使用不当线程池死锁怎么解决?
在Java多线程编程中,线程池有助于提升性能与资源利用效率,但若父子任务共用同一池,则可能诱发死锁。本文通过一个具体案例剖析此问题:在一个固定大小为2的线程池中,父任务直接调用`outerTask`,而`outerTask`再次使用同一线程池异步调用`innerTask`。理论上,任务应迅速完成,但实际上却超时未完成。经由`jstack`输出的线程调用栈分析发现,线程陷入等待状态,形成“死锁”。原因是子任务需待父任务完成,而父任务则需等待子任务执行完毕以释放线程,从而相互阻塞。此问题在测试环境中不易显现,常在生产环境下高并发时爆发,重启或扩容仅能暂时缓解。
|
4月前
|
缓存 安全 Java
Java中的线程安全问题及解决方案
Java中的线程安全问题及解决方案
|
4月前
|
消息中间件 算法 Java
(十四)深入并发之线程、进程、纤程、协程、管程与死锁、活锁、锁饥饿详解
本文深入探讨了并发编程的关键概念和技术挑战。首先介绍了进程、线程、纤程、协程、管程等概念,强调了这些概念是如何随多核时代的到来而演变的,以满足高性能计算的需求。随后,文章详细解释了死锁、活锁与锁饥饿等问题,通过生动的例子帮助理解这些现象,并提供了预防和解决这些问题的方法。最后,通过一个具体的死锁示例代码展示了如何在实践中遇到并发问题,并提供了几种常用的工具和技术来诊断和解决这些问题。本文旨在为并发编程的实践者提供一个全面的理解框架,帮助他们在开发过程中更好地处理并发问题。
|
4月前
|
设计模式 安全 Java
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
114 0