7-线程死锁

线程死锁

  • 什么是线程死锁?死锁指的是两个或两个以上的线程在执行过程中因为争夺资源而造成的互相等待的现象。在无外力的情况下,这些线程一直会相互等待而无法继续进行工作。如图:

  • image

  • 在上图中,线程A已经持有了资源2,它同时还想申请资源1,线程B已经持有了资源1,它同时还想申请资源2,所以线程A和线程B就因为相互等待对方已经持有的资源,而进入死锁状态。

  • 为什么产生死锁呢?死锁产生的必备条件:

    • 互斥条件:指线程对已经获得到的资源进行排它使用,如果还有其他线程想用这个资源,则必须等待。
    • 请求并持有条件:指一个线程已经持有了至少一个资源,但又提出了新的资源请求,而新的资源请求已经被其他线程锁占有且为互斥。
    • 不可剥夺条件:指线程获取到的资源在自己使用完之前不能被其他线程所抢占,只有在自己使用完后才能由自己释放。
    • 环路等待条件:指发生死锁的时候必然存在一个线程-资源的环形链,即线程集合{T0,T1,T2,...,Tn}中的T0等待T1占用的资源,T1等待T2占用的一个资源,以此类推,Tn等待T0占用的一个资源。
    package com.heiye.learn1;
    
    import java.util.logging.Logger;
    
    public class DeadLockTest2 {
        //创建资源
        private static Object resourceA = new Object();
        private static Object resourceB = new Object();
    
        public static void main(String[] args) {
            Logger logger = Logger.getLogger(DeadLockTest2.class.toString());
            //创建线程A
            Thread threadA = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (resourceA) {
                        logger.info(Thread.currentThread() + " get ResourceA");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        logger.info(Thread.currentThread() + " waiting get ResourceB");
                        synchronized (resourceB) {
                            logger.info(Thread.currentThread() + "get ResourceB");
                        }
                    }
                }
            });
    
            //创建线程B
            Thread threadB = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (resourceB) {
                        logger.info(Thread.currentThread() + " get ResourceB");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        logger.info(Thread.currentThread() + " waiting get ResourceA");
                        synchronized (resourceA) {
                            logger.info(Thread.currentThread() + "get ResourceA");
                        }
                    }
                }
            });
    
            //启动线程
            threadA.start();
            threadB.start();
        }
    }
    
  • image

  • Thread-2是线程B,Thread-1是线程A,从输出结果可知,线程调度器首先调度了线程B,也就是吧CPU时间片资源分配给了B,线程B通过synchronized(ResourceB)获取到了B资源锁,然后休眠1s是为了保证线程B为了获取资源A之前先让线程A获取资源A,这样线程A获取到了资源A,线程B获取到了资源B,但是线程暗想获取资源B的资源,线B又想获取资源A的资源,这样就互相等待想入了死锁。

  • 那么如何避免线程死锁呢?只需要破坏掉至少一个构造死锁的条件就可以了。造成死锁的原因其实和申请自愿的顺序有着很大的关系,使用资源申请的有序性原则就可以避免死锁,那么什么是资源申请的有序性?我们对线程B的代码进行修改

            //创建线程B
            Thread threadB = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (resourceA) {
                        logger.info(Thread.currentThread() + " get ResourceB");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        logger.info(Thread.currentThread() + " waiting get ResourceA");
                        synchronized (resourceB) {
                            logger.info(Thread.currentThread() + "get ResourceA");
                        }
                    }
                }
            });
    
  • image

  • 在上面的代码中,假如线程A和线程B同时执行了synchronized(resourceA),只有一个线程可以获取到resourceA上的监视器,假如线程A获取到了,那么线程B就会阻塞而不会再去获取资源B,线程A获取到resourceA的监视器后回去申请resourceB资源,这时候线程A是获取到的,线程A获取到resourceB并使用完之后会放弃对资源resourceB的持有,然后再释放resourceA的持有,释放resourceA后线程B才会被从阻塞阶段变为激活状态。所以资源的有序性破坏了资源的请求并持有条件和环路等待条件,因此避免了死锁。

posted @   LilyFlower  阅读(210)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示