目录
一、死锁。
(1)实际生活"死锁"情景。
(2)程序中举例。
(3)死锁产生必要的条件。
<1> 互斥使用。
<2> 不可抢占。
<3> 请求和保持。
<4> 循环等待。
(4)避免死锁。
二、IDEA中写简单"死锁程序"。
<0>分析与解释。
<1>简单死锁程序(类DeadLockDemo)
<2>测试类(Test)
<3>程序执行结果。(发生"死锁")
<4>破坏上面的"简单死锁程序"。
(I)破坏互斥条件。
(II)破坏不可抢占条件。
(III)破坏请求和保持条件。
(IIII)破坏循环等待条件。
三、总结
(1)关于死锁。
一、死锁。
(1)实际生活"死锁"情景。
- 有这样一个场景:一个中国人和一个美国人在一起吃饭。
- 其中美国人拿了中国人的筷子,中国人拿了美国人的刀叉,于是两个人开始争执不休。
- 中国人:”你先给我筷子,我再给你刀叉!“
- 美国人:“你先给我刀叉,我再给你筷子!”
- .......
(结果可想而知,两个人都吃不成饭...)
- 这个例子中的中国人和美国人相当于不同的"线程",而筷子和刀叉就相当于"锁"。而两个线程在运行的时候都在等待对方的"锁",这样就造成了程序的停滞。
- 这种现象被称为"死锁"。
(2)程序中举例。
- 有两个线程。thread01、thread02。
- thread01:a锁——>b锁。thread02:b锁——>a锁。
- thread01获取完a锁,执行完毕后不释放a锁,接着thread01又去获取b锁。
- 而thread02获取完b锁,执行完毕后不释放b锁,接着thread02有去获取a锁。
- 两个线程同时去执行,就会造成"死锁"情况。
(3)死锁产生必要的条件。
<1> 互斥使用。
- 互斥(排它性)。即当资源被一个线程使用(占有)时,别的线程不能使用。
- 像同步代码块、同步方法一样,拿到"锁"时,其它线程不能再进去访问或使用。
<2> 不可抢占。
- 资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
- 也就是拿到这个线程后,另外的线程不能抢"锁"。
- 如果各个线程可以"抢锁",就算前面抢到"锁"的线程因为某种原因无法释放"锁",后面等待的线程也可以去抢到"锁",也就不会造成阻塞(死锁)。
<3> 请求和保持。
- 即当资源请求者在请求其他的资源的同时保持对原有资源的占用。
- 如上的两个线程"thread01"、"thread02"。thread01拿到a锁,用完就释放掉,而thread02拿到b锁,用完也释放掉。这样再互相拿自己需要的"锁",就不会造成"死锁"现象。
<4> 循环等待。
- 即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
- 而上面的例子也是一个小等待队列(环)。thread01->a->b->thread02->b->a。
- 如果改成thread01:a锁——>b锁。thread02:a锁——>b锁。也不会造成死锁的情况了。
(注意:上述的四个条件都满足,才会造成"死锁"的产生。)
(4)避免死锁。
- 在实际开发中,为了避免"死锁"的发生,只需要把上述四个条件中的任意一个打破,就可以避免"死锁"的现象产生。
二、IDEA中写简单"死锁程序"。
(也就是整个程序都满足上述的四个条件)
<0>分析与解释。
- "死锁类"不采用实现Runnable接口,重新run()方法。而是在测试中直接使用匿名的Runnable实现子类。()->{}。中括号里面就是实现的run()方法。
- "死锁类"创建的两个类的锁对象(static)。在该"死锁"类的new两个即可。保证a与b"锁"是这个"死锁类"是共享的。
- 测试类中到时候模拟两个线程,同时new两个"死锁"类的对象。d1、d2是不共享的,但是锁a、锁b是共享的资源。
- 测试类中的new Thread(Runnable target)。参数内部是使用Lambda表达式(也可以使用匿名内部类)也就是在重新的run()方法里面进行调用方法操作(d1.执行死锁方法、d2.执行死锁方法)
- 测试类中最后两个线程分别去使用start()方法。然后分别去拿各自的"锁a"、"锁b",然后阻塞(哈哈哈!)
- "死锁类"中这里注意还需要有一个布尔类型的变量。去控制获取锁的顺序。顺序????就是为"true"时控制一个线程拿"锁"时,从a锁——>b锁。值为false时让另外一个线程拿"锁"时,从b锁——>a锁。flag不是设置成共享的(不是static)。
(具体详细代码如下。)
<1>简单死锁程序(类DeadLockDemo)
package com.fs;/*** @Title: DeadLockDemo* @Author HeYouLong* @Package com.fs* @Date 2024/10/12 下午8:41* @description: 简单死锁程序演示*/ public class DeadLockDemo {//锁a与锁bprivate static Object a = new Object();private static Object b = new Object();//控制拿锁的顺序,a——>b或者b——>aprivate boolean flag;//提供构造方法改布尔值public DeadLockDemo(boolean flag) {this.flag = flag;}public void testDeadLock() throws InterruptedException {if(flag){//a——>bsynchronized (a){System.out.println(Thread.currentThread().getName()+"执行a锁代码块的资源");Thread.sleep(100); //模拟执行完后等待synchronized (b){System.out.println(Thread.currentThread().getName()+"执行b锁代码块的资源");Thread.sleep(100); //模拟执行完后等待}}}else {//b——>asynchronized (b){System.out.println(Thread.currentThread().getName()+"执行b锁代码块的资源");Thread.sleep(100); //模拟执行完后等待synchronized (a){System.out.println(Thread.currentThread().getName()+"执行a锁代码块的资源");Thread.sleep(100); //模拟执行完后等待}}}} }
<2>测试类(Test)
package com.fs; /*** @Title: Test* @Author HeYouLong* @Package com.fs* @Date 2024/10/13 上午10:59* @description: 测试类*/ public class Test {public static void main(String[] args) {//创建不同对象DeadLockDemo d1 = new DeadLockDemo(true);DeadLockDemo d2 = new DeadLockDemo(false);Thread t1 = new Thread(()->{//线程t1的run()方法使用d1对象//但是在对象调用testDeadLock()使用的是共享资源的锁try {d1.testDeadLock();} catch (InterruptedException e) {throw new RuntimeException(e);}},"线程1");Thread t2 = new Thread(()->{//线程t2的run()方法使用d2对象// 但是在对象调用testDeadLock()使用的是共享资源的锁try {d2.testDeadLock();} catch (InterruptedException e) {throw new RuntimeException(e);}},"线程2");//启动线程t1.start();t2.start();} }
<3>程序执行结果。(发生"死锁")
<4>破坏上面的"简单死锁程序"。
(I)破坏互斥条件。
(II)破坏不可抢占条件。
(III)破坏请求和保持条件。
- 让两个线程"t1"、"t2"都执行完资源后释放完各自的"锁",然后再去申请其它"锁"。(不允许它保持)
public void testDeadLock() throws InterruptedException {if(flag){//a——>bsynchronized (a){System.out.println(Thread.currentThread().getName()+"执行a锁代码块的资源");Thread.sleep(100); //模拟执行完后等待}synchronized (b){System.out.println(Thread.currentThread().getName()+"执行b锁代码块的资源");Thread.sleep(100); //模拟执行完后等待}}else {//b——>asynchronized (b){System.out.println(Thread.currentThread().getName()+"执行b锁代码块的资源");Thread.sleep(100); //模拟执行完后等待}synchronized (a){System.out.println(Thread.currentThread().getName()+"执行a锁代码块的资源");Thread.sleep(100); //模拟执行完后等待}}}
(IIII)破坏循环等待条件。
- 简单修改即可。不让他们请求锁时形成环路。(把顺序都变成"锁a"——>"锁b")
三、总结
(1)关于死锁。
- "死锁"现象造成,必须四个条件都满足了。只要破坏其中一个条件,"死锁"的现象就不存在了。
- 以后写代码,尽量让加锁的顺序保持一致。或者某个线程操作多个锁,操作完成之后,要让它释放掉。
- 在实际开发中,只需要把上述四个条件任意一个打破,就可以最大可能的避免死锁。