“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

198 阅读8分钟

难度

初级

学习时间

30分钟

适合人群

零基础

开发语言

Java

开发环境

  • JDK v11
  • IntelliJ IDEA v2018.3

友情提示

  • 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
  • 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!

1.温故知新

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

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

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

《“全栈2019”Java多线程第十九章:死锁详解》一章中介绍了死锁

《“全栈2019”Java多线程第二十章:同步方法产生死锁的例子》一章中介绍了同步方法产生死锁的例子

现在我们来讲解同步代码块产生死锁的例子

2.同步代码块中的锁

通过《“全栈2019”Java多线程第十七章:同步锁详解》一章的学习,我们知道了同步代码块有两种同步锁:对象锁和类锁(类名.class)

对象锁:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

上图中的对象锁是类型为Object的lock对象。

当然了,同步对象也可以是this关键字:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

非静态作用域(包括代码块和方法)内同步代码块的同步锁是对象锁

类锁(类名.class):

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

静态作用域(包括代码块和方法)内同步代码块的同步锁是对象锁

3.死锁章节代码演示

其实我们在讲解《“全栈2019”Java多线程第十九章:死锁详解》一章时,里面的死锁程序就是用同步代码块来演示的,这里就不再赘述程序一步一步是怎么来的了,希望不清楚的小伙伴可以前去看看。

本小节只是将死锁章节中例子演示一次,然后我们在下一小节一步一步来写一个更加简化的例子,方便我们理解记忆,以后碰到要求写一个同步代码块死锁例子面试题时,可以顺利作答。

演示:

请写一个由同步代码块产生的死锁程序。

请观察程序代码及结果。

代码:

Girl类:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

Boy类:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

DeadLock类:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

Main类:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

结果:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

从运行结果来看,死锁程序完美形成。

下面,我们自己来写一个死锁,简化上面的程序。

4.同步代码块死锁程序

死锁的第一个必要条件是两个或两个以上的线程,于是我们在Main类中创建出两个线程:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

死锁的第二个必要条件是两把或两把以上的同步锁,我们分两个步骤来,第一个步骤先写一个DeadLock类并让其实现Runnable接口:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

然后,在DeadLock类中定义两个同步锁(对象锁):

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

因为是对象锁,所以所有Java对象都可以是同步对象。这里写Object类型的对象更方便。

需要注意的是,建议将同步锁用final关键字修饰

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

同步锁搞定,接下来书写同步代码块:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

因为一个同步代码块只能有一个同步对象,所以这里自然而然需要两个同步代码块:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

单单把这两个同步代码块依次罗列在这毫无意义,因为它产生不了死锁。得让两个线程进来以后一人拿一把锁

怎么让线程进来一人拿一把锁呢?

可以通过if-else语句来实现,通过判断条件来执行不同的同步代码块即可:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

这样通过不同条件的值让两个线程执行不同的同步代码块,拿不同的同步锁

这里的判断条件就用一个boolean类型的变量实现好了:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

当flag为true时,线程执行同步对象为lock1的同步代码块,持有的同步锁是lock1:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

当flag为false时,线程执行同步对象为lock2的同步代码块,持有的同步锁是lock2:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

至此,两个线程都各自持有一把锁,接下来就是让两个线程相互竞争对方的锁即可,死锁也就产生了。

怎么竞争呢?

我们可以在同步对象为lock1的同步代码块中执行同步对象为lock1的同步代码块内容,同理,在同步对象为lock2的同步代码块中执行同步对象为lock1的同步代码块内容

先来实现在同步对象为lock1的同步代码块中执行同步对象为lock1的同步代码块内容

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

再来实现在同步对象为lock2的同步代码块中执行同步对象为lock1的同步代码块内容

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

这样竞争关系也写好了。

不过程序看上去少点什么,少点输出文字,我们来给它加上:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

我们分别在几个同步代码块里面输出一句话,以示区分。

好像还有一件非常重要的事情没有做,那就是改变flag变量的值形成切换效果。

具体做法是在if语句里面将flag变量的值设为false,在else语句里面将flag变量的值设为true:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

如此一来,当第一个线程进来执行到if语句时,它会将flag的值变为false,后来进行的线程再判断flag的时候就会去执行else语句,这样切换分支效果就做好了。

最后,我们来把Main类完善起来,之前Main类的main()方法里面只创建了两个线程,现在要创建一个DeadLock对象:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

接着,把deadLock1对象传递给线程thread1和thread2:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

然后,我们启动线程即可:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

至此,我们把之前的代码整理之后,再运行程序看看。

演示:

请写一个由同步代码块产生的死锁程序。

请观察程序代码及结果。

代码:

DeadLock类:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

Main类:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

结果:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

从运行结果来看,我们成功了。

同步代码块产生死锁的程序写完了,最后希望大家可以在面试的时候可以顺利通过此类题目,同时也希望大家在以后的开发中可以避免死锁。

GitHub

本章内容GitHub地址:https://github.com/gorhaf/Java2019/tree/master/Thread/DeadLock

附:一个类写完死锁程序(选读)

一个类可以可以写完死锁程序,就是代码看起来有点挤,阅读性太差。不建议大家这样写。本小节为选读,感兴趣的小伙伴可以继续阅读,不感兴趣的小伙伴则可以跳过,跳过对本章内容没有任何影响。

演示:

请写一个由同步代码块产生的死锁程序。

请观察程序代码及结果。

代码:

Main类:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

结果:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

从运行结果来看,死锁也产生了。

这种写法就是采用内部类的形式实现的。

当然了,你也可以把同步锁拿出来放在main()方法中:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

运行程序,执行结果:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

结果是一样的。

你还可以把同步锁移到Main类中去,作为成员变量:

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

大家需要注意的是,同步锁一旦变为成员变量,注意看同步代码块所在的作用域是否为静态的,如果是静态的作用域,那么我们的同步锁也必须是静态或者是类锁。

好了,附加的内容也说完了,最后祝大家书写代码开心。

总结

  1. 准备一个类,并实现Runnable接口。
  2. 类中准备两个同步锁和一个boolean类型的变量。
  3. 在if语句里面将boolean变量的值设为false,在else语句里面将boolean变量的值设为true,作用是切换if-else分支。
  4. 切换if-else分支的目的是让不同线程去执行不同同步代码块,从而拿到不同的锁。
  5. 接着让两个同步代码块相互执行对方一次,这样做的目的是让线程相互竞争。
  6. 然后在Main类的mian()方法中创建一个类的实例和两个或两个以上线程对象,并将类的实例传递给这些线程。
  7. 最后启动这些线程即可。

至此,Java中同步代码块产生死锁的例子相关内容讲解先告一段落,更多内容请持续关注。

答疑

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

上一章

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

下一章

“全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解

学习小组

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

  • 方式一:关注头条号Gorhaf,私信“Java学习小组”。
  • 方式二:关注公众号Gorhaf,回复“Java学习小组”。

全栈工程师学习计划

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

“全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子

版权声明

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