难度

初级

学习时间

30分钟

适合人群

零基础

开发语言

Java

开发环境友情提示1.温故知新

在《“全栈2019”Java多线程第十六章:同步synchronized关键字详解》一章中介绍了synchronized关键字

在《“全栈2019”Java多线程第十七章:同步锁详解》一章中介绍了同步代码块/方法中的同步锁

在《“全栈2019”Java多线程第十八章:同步代码块双重判断详解》一章中介绍了同步代码块中的双重判断

现在我们来讲解死锁

2.死锁定义

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。

对于死锁的定义不明白的小伙伴先不用着急,下面我们会通过第3小节的漫画和第6小节的分词解释 举例说明来理解死锁。

3.单线程不会发生死锁

死锁定义的第一句为“死锁是指两个或两个以上的线程在执行过程中”,从中可以得知死锁发生的必要条件是两个或两个以上的线程,这也就说明了单线程不会发生死锁。

为什么单线程不会发生死锁?

死锁定义的第二句也进行了解释“由于竞争资源或者由于彼此通信而造成的一种阻塞的现象”。其中有两个词需要注意:“竞争资源”和“彼此通信”。

竞争资源

java线程死锁的产生以及避免(Java多线程第十九章)(1)

售票窗口是线程。

车票是资源。

几个售票窗口同时在卖票就好比是几个线程同时在竞争执行卖票。

彼此通信

java线程死锁的产生以及避免(Java多线程第十九章)(2)

坐在桌子前打包的人是线程A。

派送货物的人是线程B。

货物是资源。

打包货物的人将货物打包完成之后,通知派送货物的人进行派送,即为彼此通信。

同理,线程A执行打包货物任务,执行完毕之后通知线程B执行派送任务,也是彼此通信。

3.图说死锁

下面,我们来通过漫画来解释说明什么是死锁。

第一句,死锁是指两个或两个以上的线程在执行过程中

如果把人看作是线程,那么我们就按照定义中说的来两个线程

java线程死锁的产生以及避免(Java多线程第十九章)(3)

左边为线程A。右边为线程B。

第二句,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象

java线程死锁的产生以及避免(Java多线程第十九章)(4)

资源是他们之间的火锅。

竞争资源即现在他们都想吃这个火锅。

但是,他们各自手里只有一根筷子:

java线程死锁的产生以及避免(Java多线程第十九章)(5)

目前还吃不了,除非其中一人把他手里的筷子先借给另外一人先吃一口,然后另外一人再把筷子借给对方吃一口,如此反复下去,大家就都有的吃:

java线程死锁的产生以及避免(Java多线程第十九章)(6)

当然了,也有相互之间不愿配合的,线程A紧握筷子不松手,线程B也紧握筷子不松手,他们两人谁都不让谁,于是形成僵局,即死锁:

java线程死锁的产生以及避免(Java多线程第十九章)(7)

大家知道例子中的筷子在线程中代表着什么吗?

例子中的筷子在线程中代表着同步锁

死锁中为什么会有两把同步锁?而且还是两个线程各自持有一把?

这两个问题我们会在第4小节为大家解答。

第三句,若无外力作用,它们都将无法推进下去

也就是说没有人为干预,死锁将一直持续下去,即僵局。

怎样人为干预?

强制一方释放锁或者是两个都释放锁,重新争夺锁。

在该例子中则为强制一方将筷子给对方。

java线程死锁的产生以及避免(Java多线程第十九章)(8)

第四句,此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程

该句总结了什么是死锁。

java线程死锁的产生以及避免(Java多线程第十九章)(9)

永远在相互等待的线程”说的就是例子中的线程A和线程B。线程A在等待线程B释放手中的筷子,线程B在等待线程A释放手中的筷子,它们永远在相互等待

前面也说了筷子代表着线程中的同步锁,所以线程A在等待线程B释放手中的同步锁,线程B在等待线程A释放手中的同步锁,它们永远在相互等待

不知道上面的漫画有没有给大家解释清楚什么是死锁,下面再来一个漫画给大家参考:

java线程死锁的产生以及避免(Java多线程第十九章)(10)

女孩想要男孩手中的棒棒糖,男孩想要女孩手中的玩具,他们两人互不相让,最终形成僵局,即死锁。

下面我们就把这个漫画通过代码示例展现出来,顺便给大家作进一步解释。

4.死锁程序

根据上一小节最后提出的要求,用代码来描述上一小节中最后漫画场景。

首先,我们用一个类来描述女孩:

java线程死锁的产生以及避免(Java多线程第十九章)(11)

接着,再用一个类描述男孩:

java线程死锁的产生以及避免(Java多线程第十九章)(12)

然后,女孩想要棒棒糖,用方法来描述一下:

java线程死锁的产生以及避免(Java多线程第十九章)(13)

接着,男孩说想要玩具,也用方法来描述一下:

java线程死锁的产生以及避免(Java多线程第十九章)(14)

然后,女孩对男孩说“你把棒棒糖给我,我就把玩具给你!”,用方法来描述一下:

java线程死锁的产生以及避免(Java多线程第十九章)(15)

接着,男孩对女孩说“你把玩具给我,我就把棒棒糖给你!”,也用方法描述一下:

java线程死锁的产生以及避免(Java多线程第十九章)(16)

因为死锁只会发生两个线程或两个线程以上,所以我们在Main类main()方法中创建两个线程:

java线程死锁的产生以及避免(Java多线程第十九章)(17)

线程有了,现在还缺资源,不能让线程什么都不执行,于是我们来了一个DeadLock类,实现Runnable接口,这样多个线程可以竞争一个资源:

java线程死锁的产生以及避免(Java多线程第十九章)(18)

资源有了,还缺两把同步锁,为什么是两把?

因为我们有两个线程,至少得有两把同步锁才可能产生死锁,所以需要两把同步锁。

问题是这两个同步锁从哪里来呢?

就从之前创建的男孩对象和女孩对象中来:

java线程死锁的产生以及避免(Java多线程第十九章)(19)

同步锁有了,我们在同步代码块里面一次只能用一把同步锁:

java线程死锁的产生以及避免(Java多线程第十九章)(20)

这两把锁必须让两个线程各拥有一把就完美了,我们可以通过if判断标识的不同来让两个线程分别来执行不同的同步代码块:

java线程死锁的产生以及避免(Java多线程第十九章)(21)

而这个判断条件就定义一个boolean变量即可:

java线程死锁的产生以及避免(Java多线程第十九章)(22)

接着,在同步对象为女孩的同步代码块中调用女孩说的话:

java线程死锁的产生以及避免(Java多线程第十九章)(23)

然后,在同步对象为男孩的同步代码块中调用男孩说的话:

java线程死锁的产生以及避免(Java多线程第十九章)(24)

话都说完了,该表明相互需要什么,大家都互不相让,形成僵局,死锁就产生了。

于是,我们在同步对象为女孩的同步代码块中表明想要的东西:

java线程死锁的产生以及避免(Java多线程第十九章)(25)

这样写还不对,为什么不对呢?

你没有表明向对方要,这样写表明是在向自己要。需要将get()方法放在一个同步对象为男孩的同步代码块中,表示向对方索要

java线程死锁的产生以及避免(Java多线程第十九章)(26)

同理,男孩也向女孩表明想要的东西:

java线程死锁的产生以及避免(Java多线程第十九章)(27)

好了,run()方法里面书写基本上差不多了,可以运行起来试试了。

在运行之前,我们需要在DeadLock类中添加一个设置标识变量flag的方法:

java线程死锁的产生以及避免(Java多线程第十九章)(28)

接着,我们去Main类main()中创建两个DeadLock类的实例:

java线程死锁的产生以及避免(Java多线程第十九章)(29)

然后,将其两个对象分别传递给thread1和thread2两个线程对象:

java线程死锁的产生以及避免(Java多线程第十九章)(30)

别忘了把两个DeadLock对象中的flag变量设置为不同的boolean值:

java线程死锁的产生以及避免(Java多线程第十九章)(31)

最后,我们来启动这两个线程:

java线程死锁的产生以及避免(Java多线程第十九章)(32)

至此,整个程序写完了,有没有问题还不知道,运行了再说。

把上面所有代码做一个整理写在下方。

演示:

请描述一个死锁程序。

请观察程序代码及结果。

代码:

Girl类:

java线程死锁的产生以及避免(Java多线程第十九章)(33)

Boy类:

java线程死锁的产生以及避免(Java多线程第十九章)(34)

DeadLock类:

java线程死锁的产生以及避免(Java多线程第十九章)(35)

Main类:

java线程死锁的产生以及避免(Java多线程第十九章)(36)

结果:

java线程死锁的产生以及避免(Java多线程第十九章)(37)

从运行结果来看,没有出现传说中的死锁。

奇了怪了,为什么没有出现死锁呢?

原因是我们在这里创建了两个DeadLock对象:

java线程死锁的产生以及避免(Java多线程第十九章)(38)

而每个DeadLock对象里面都有各自的两把同步锁:

java线程死锁的产生以及避免(Java多线程第十九章)(39)

即两个DeadLock对象一共有四把同步锁,一个DeadLock对象有两把同步锁。

现在要把两个同步锁弄成唯一的即可,在原有同步锁的基础上加上static final,只加上static也行,但建议加上final

java线程死锁的产生以及避免(Java多线程第十九章)(40)

好了,其他类不变。

运行程序,执行结果:

java线程死锁的产生以及避免(Java多线程第十九章)(41)

从运行结果来看,这一次我们成功了。两个线程相互竞争资源导致阻塞,形成死锁。

5.只使用一个DeadLock对象资源行不行?

有小伙伴说:只使用一个DeadLock对象资源行不行?

答案是可以的。

java线程死锁的产生以及避免(Java多线程第十九章)(42)

大家也看到了,当我们使用一个DeadLock对象资源时,设置flag变量变得没有意义,所以我们的DeadLock类也需要修改:

java线程死锁的产生以及避免(Java多线程第十九章)(43)

移除了setFlag()方法。

那么flag变量的值该怎么去切换呢?

我们可以在if和else里面进行切换:

java线程死锁的产生以及避免(Java多线程第十九章)(44)

if表达式条件里面的flag一定为true,所以在if里面将其设置为false;

else表达式条件里面flag一定为false,所以在else里面将其设置true;

这样一来,flag变量的值一直在切换。

其他类不变,运行程序,执行结果:

java线程死锁的产生以及避免(Java多线程第十九章)(45)

死锁依旧产生,说明一个DeadLock资源对象也是可以的。

既然只有一个DeadLock资源对象,那么DeadLock类里面修饰同步锁的static关键字也可以拿掉了:

java线程死锁的产生以及避免(Java多线程第十九章)(46)

你不拿掉也行。这个看大家的需求。

6.分词解释&举例说明

死锁的完整定义:

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。

下面开始分词解释&举例说明。

第一句,死锁是指两个或两个以上的线程在执行过程中

对应示例代码中的thread1和thread2两个线程:

java线程死锁的产生以及避免(Java多线程第十九章)(47)

第二句,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象

竞争一个资源:

java线程死锁的产生以及避免(Java多线程第十九章)(48)

相互索要对方的同步锁:

java线程死锁的产生以及避免(Java多线程第十九章)(49)

最关键的代码是:

java线程死锁的产生以及避免(Java多线程第十九章)(50)

这两处是整个死锁程序的命脉。

一个同步对象的同步代码块里面去执行另一个同步对象的同步代码块

第三句,若无外力作用,它们都将无法推进下去

也就是两个线程相互索要对方的同步锁,两个线程阻塞,形成僵局,产生死锁。

第四句,此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程

该句旨在总结前面的现象产生了死锁。

最后,希望大家在日常开发中能够避免死锁。

总结
  • 死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。

至此,Java中死锁相关内容讲解先告一段落,更多内容请持续关注。

答疑

如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。

上一章

“全栈2019”Java多线程第十八章:同步代码块双重判断详解

下一章

“全栈2019”Java多线程第二十章:同步方法产生死锁的例子

学习小组

加入同步学习小组,共同交流与进步。

  • 方式一:关注头条号Gorhaf,私信“Java学习小组”。
  • 方式二:关注公众号Gorhaf,回复“Java学习小组”。
全栈工程师学习计划

关注我们,加入“全栈工程师学习计划”。

java线程死锁的产生以及避免(Java多线程第十九章)(51)

版权声明

原创不易,未经允许不得转载!

,