自学内容网 自学内容网

Java多线程之死锁(死锁产生条件、手写简单死锁程序、破坏死锁)(面试常有)

目录

一、死锁。

(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与锁b
    private static Object a = new Object();
    private static Object b = new Object();

    //控制拿锁的顺序,a——>b或者b——>a
    private boolean flag;
    //提供构造方法改布尔值

    public DeadLockDemo(boolean flag) {
        this.flag = flag;
    }

    public void testDeadLock() throws InterruptedException {
        if(flag){
            //a——>b
            synchronized (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——>a
            synchronized (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——>b
            synchronized (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——>a
            synchronized (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)关于死锁。
  • "死锁"现象造成,必须四个条件都满足了。只要破坏其中一个条件,"死锁"的现象就不存在了。
  • 以后写代码,尽量让加锁的顺序保持一致。或者某个线程操作多个锁,操作完成之后,要让它释放掉。
  • 在实际开发中,只需要把上述四个条件任意一个打破,就可以最大可能的避免死锁。

原文地址:https://blog.csdn.net/m0_74363339/article/details/142885031

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!