线程死锁、锁死、饥饿、活锁讲解

news2024/11/15 23:50:59

文章目录

  • 死锁
    • 哲学家就餐问题
    • 死锁的检测方式
    • 死锁的产生条件
    • 死锁的规避
    • 死锁的恢复
  • 锁死
    • 信号丢失锁死
    • 嵌套监视器锁死
  • 线程饥饿
  • 活锁

死锁

概念

如果两个或者更多的线程因为相互等待对方而被永远暂停,线程的生命周期变成了BLOCKED或者WAITING,则我们称这些线程产生了死锁(DeadLock)

哲学家就餐问题

现在来用代码描述一下“哲学家就餐问题”,先将问题简化,假设只有两个哲学家面对面坐着,每个哲学家吃饭都先拿自己左手边的筷子,再拿右手边;当哲学家A拿起他左手边的的筷子,打算拿右边的时,哲学家B拿起了他自己左手边的筷子,而其正好是哲学家A的右手边,由于双方都等着拿自己右手边筷子,但自己又不会先放下左手中的筷子,这就导致了“死锁”。
1、哲学家的抽象类

public abstract class AbsPhilosopher extends Thread {

    protected String id;
    protected Chopstick left;
    protected Chopstick right;


    public AbsPhilosopher(String id, Chopstick left, Chopstick right) {
        this.id = id;
        this.left = left;
        this.right = right;
    }


    @Override
    public void run() {
        for (; ; ) {
            think();
            eat();
        }

    }

    protected abstract void eat();

    protected void think() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("the %s is thinking\n", id);
    }

    protected void doEat() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.printf("the %s is eating\n", id);
    }
}

2、具有死锁的哲学家实现类

public class DeadLockPhilosopherImpl extends AbsPhilosopher {


    public DeadLockPhilosopherImpl(String id, Chopstick left, Chopstick right) {
        super(id, left, right);
    }

    @Override
    protected void eat() {
        synchronized (left) {
            left.pickup();
            synchronized (right) {
                right.pickup();
                //两根筷子同时拿起开始吃饭
                doEat();
                right.putDown();
            }
            left.putDown();
        }
    }
}

3、筷子类

public class Chopstick {

    public final int id;

    public Chopstick(int id) {
        this.id = id;
    }

    public void pickup(){
    }

    public void putDown(){

    }
}

4、客户端

public class PhilosopherTest {

    public static void main(String[] args) {

        int nums = 2;
        //两个人,两只筷子
        Chopstick[] chopsticks = new Chopstick[nums];

        for (int i = 0; i < 2; i++) {
            chopsticks[i] = new Chopstick(i);
        }

        //创建两个哲学家
        for (int i = 0; i < nums; i++) {
            AbsPhilosopher philosopher0 = new DeadLockPhilosopherImpl(String.valueOf(i), chopsticks[i], chopsticks[(i + 1) % nums]);
            AbsPhilosopher philosopher1 = new DeadLockPhilosopherImpl(String.valueOf(i), chopsticks[i], chopsticks[(i + 1) % nums]);

            philosopher0.start();
            philosopher1.start();
        }

    }
}

死锁的检测方式

上述问题出现了死锁,有两种检测方式
1、启动客户端之后,在控制台输入jps,找到启动类的pid,进行jstack,即可发现死锁的产生:
在这里插入图片描述
2、另外一种本地检查死锁的方式,如果是win电脑的话,可以按win+r,输入jconsole,选择执行的demo后按如下操作可以快速定位死锁。
在这里插入图片描述

死锁的产生条件

资源互斥

涉及的资源必须是独立的,即一个资源只能被一个线程访问。比如一根筷子只能被一个哲学家使用。

资源不可抢夺

涉及到的资源只能被线程持有者主动释放,其他线程无法抢夺。比如筷子只能由当前哲学家进行放下,其他哲学家不能抢夺。

占用并等待资源

涉及到的线程当前至少持有一份资源,并等待其他线程持有的资源,在资源等待中,当前线程并不释放自己持有的资源。比如哲学家A拿起自己左手的筷子,并申请他右手边的筷子,但并不会放下左手中的筷子。

循环等待资源

涉及的线程必须在等待别的线程的资源,而这些线程又等待第一个线程的资源。第一个哲学家在等待第二个哲学家左手的筷子,第二个哲学家在等待第一个哲学家的左手的筷子。

死锁的规避

死锁的四个产生条件时必要不充分条件,产生死锁的话,这四个条件一定要同时成立,但这四个条件同时成立不一定产生死锁。所以只要消除死锁的任意一个条件就可以规避死锁。
粗锁法:使用一个粗粒度的锁来代替多个锁,从而消除“占用并等待资源”和“循环等待资源”这两个条件。

public class GlobalLockPhilosopherImpl extends AbsPhilosopher {
	//使用static变量
    private static final Object GLOBAL_LOCK = "lock";

    public GlobalLockPhilosopherImpl(String id, Chopstick left, Chopstick right) {
        super(id, left, right);
    }
    @Override
    protected void eat() {
        synchronized (GLOBAL_LOCK) {
            left.pickup();
            right.pickup();
            //两根筷子同时拿起开始吃饭
            doEat();
            right.putDown();
            left.putDown();
        }
    }
}

粗锁法有一个缺点是会导致资源的浪费,因为同一时刻只能有一个线程使用资源,也就是同一时刻只能有一个哲学家就餐。假设有5个哲学家,当有1个哲学家就餐时,还剩下3根筷子,其实还够一个哲学家就餐的。

锁排序法:相关线程使用全局统一的顺序去申请锁。假设有多个线程要去申请资源,则可以给这些资源排上序号,让这些线程按照序号从小到大或者从大到小去申请,则可以打破“循环等待资源”这个条件。哲学家就餐导致死锁的一个原因是哲学家必须先拿起自己左手的筷子,再拿起右手的筷子。我们给这些筷子排上序号,当一个哲学家就餐时,他必须先拿起序号小的筷子,再拿起序号大的筷子,而不是先左手再右手。

public class SortLockPhilosopherImpl extends AbsPhilosopher {

    private Chopstick one;
    private Chopstick theOther;

    private static final Object GLOBAL_LOCK = "lock";

    public SortLockPhilosopherImpl(String id, Chopstick left, Chopstick right) {
        super(id, left, right);
		//对资源进行排序
        int leftHash = Objects.hash(left);
        int rightHash = Objects.hash(right);

        if (leftHash < rightHash) {
            one = left;
            theOther = right;
        } else if (leftHash > rightHash) {
            one = right;
            theOther = left;
        } else { //hash相同
            one = null;
        }
    }

    @Override
    protected void eat() {
        if (Objects.nonNull(one)){
            synchronized (one){
                one.pickup();
                synchronized (theOther){
                   theOther.pickup();
                    eat();
                    theOther.putDown();
                }
                one.putDown();
            }
        }else{
            synchronized (GLOBAL_LOCK) {
                left.pickup();
                right.pickup();
                doEat();
                right.putDown();
                left.putDown();
            }
        }
    }
}

使用ReentrantLock.tryLock(long,TimeUnit):为申请锁设置一个超时时间,在超时时间内,如果获取锁则返回true,没有则返回false。可以打破“占用并等待资源这个条件”。

public class TryLockPhilosopherImpl extends AbsPhilosopher {
    private static Map<Chopstick, ReentrantLock> LOCK_MAP = new ConcurrentHashMap<>();

    public TryLockPhilosopherImpl(String id, Chopstick left, Chopstick right) {
        super(id, left, right);
        LOCK_MAP.putIfAbsent(left, new ReentrantLock());
        LOCK_MAP.putIfAbsent(right, new ReentrantLock());

    }

    @Override
    protected void eat() {
        if (pickUp(left) && pickUp(right)) {
            try {
                doEat();
            } finally {
                putDown(left);
                putDown(right);
            }
        }
    }

    private boolean pickUp(Chopstick chopstick) {
        ReentrantLock lock = LOCK_MAP.get(chopstick);
        boolean tryLock = false;
        try {
            tryLock = lock.tryLock(30, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
            //当前线程拿起另一只筷子,使其放下
            Chopstick theOther = chopstick == left ? right : left;
            if (LOCK_MAP.get(theOther).isHeldByCurrentThread()) {
                theOther.putDown();
                LOCK_MAP.get(theOther).unlock();
            }
            return false;
        }
        if (tryLock) {
            chopstick.pickup();
            return true;
        } else {
            return false;
        }
    }

    private void putDown(Chopstick chopstick) {
        ReentrantLock lock = LOCK_MAP.get(chopstick);
        chopstick.putDown();
        lock.unlock();
    }
}

死锁的恢复

以上是死锁的规避方法,但如果死锁已经产生了,需要具有恢复的手段。如果代码使用的是内部锁或者Lock.lock(),则无法恢复,只能重启虚拟机。但如果代码中使用的是Lock.lockInterruptibly()实现的,那是可以进行恢复的。

public class DeadLockDetector extends Thread {
    static final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    private final long monitorInterval;

    public DeadLockDetector(long monitorInterval) {
        super("DeadLockDetector");
        setDaemon(true);
        this.monitorInterval = monitorInterval;
    }

    public DeadLockDetector() {
        this(2000);
    }

    public static ThreadInfo[] findDeadLockThreads() {
        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
        return null == deadlockedThreads ? new ThreadInfo[0] : threadMXBean.getThreadInfo(deadlockedThreads);
    }

    public static Thread findThreadById(long threadId) {
        for (Thread thread : Thread.getAllStackTraces().keySet()) {
            if (thread.getId() == threadId) {
                return thread;
            }
        }
        return null;
    }

    public static boolean interruptThread(long threadId) {
        Thread thread = findThreadById(threadId);
        if (null != thread) {
            thread.interrupt();
            return true;
        }
        return false;
    }

    @Override
    public void run() {
        ThreadInfo[] threadInfos;
        ThreadInfo threadInfo;
        int i = 0;
        for (; ; ) {
            //检测系统中是否存在死锁
            threadInfos = DeadLockDetector.findDeadLockThreads();
            if (threadInfos.length > 0) {
                //选取任意一个死锁的线程,给它发送中断请求
                threadInfo = threadInfos[i++ % threadInfos.length];
                DeadLockDetector.interruptThread(threadInfo.getThreadId());
                continue;
            } else {
                i = 0;
                System.out.println("No deadLock found");
            }
            try {
                //每隔一段时间进行一次死锁的检测
                Thread.sleep(monitorInterval);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

大概思想是建立一个心跳线程,间断性的去检测jvm有没有死锁产生,有的话就发送一个中断请求。
所以把哲学家就餐中加锁的代码换成如下:

    private boolean pickUp(Chopstick chopstick) {
        ReentrantLock lock = LOCK_MAP.get(chopstick);
        try {
            lock.lockInterruptibly();
            chopstick.pickup();
            return true;
        } catch (InterruptedException e) {
            //当前线程拿起另一只筷子,使其放下
            Chopstick theOther = chopstick == left ? right : left;
            theOther.putDown();
            LOCK_MAP.get(theOther).unlock();
            return false;
        } finally {
            lock.unlock();
        }
    }

锁死

由于唤醒等待线程所需的条件永远无法成立,或者其他线程无法唤醒该线程而一直处于非运行状态,我们称这个线程被锁死。
锁死和死锁的最终结果很相似,都是线程无法运行。但二者产生的条件是不同的,死锁产生的条件在如上四个;锁死产生的条件分为信号丢失锁死和嵌套监视器锁死。

信号丢失锁死

信号丢失锁死是没有相应的通知线程来唤醒等待线程,从而使线程一直等待下去。最常见的例子就是线程执行wait()/notify()的时候,没有对保护条件进行判断,但先执行了nofity,再执行的wait,从而错过了线程唤醒,导致等待线程一直等待下去。错误的代码如下:

  synchronized (someObject) {
            //调用someObject来暂停当前线程
            someObject.wait();
            //其它线程执行了notify,使条件满足时,执行目标动作
            doAction();
        }

正确的代码如下:

   //原子操作,在调用之前需要新获取内部锁
    synchronized (someObject){
        while(保护条件不成立){
            //调用someObject来暂停当前线程
            someObject.wait()
        }
        //其它线程执行了notify,使条件满足时,执行目标动作
        doAction();
    }

嵌套监视器锁死

嵌套监视器锁死是嵌套锁导致等待线程永远无法被唤醒的一种活性障碍。
在这里插入图片描述
如上情况,左边是等待线程,右边是通知线程。当语句2准备执行的时候,语句1已经在执行了,从而导致通知线程被阻塞,当等待线程执行完语句1之后,并不会释放monitorX,继续执行到monitorY.wait()处,从而使等待线程阻塞,但是因为等待线程没有释放monitorX资源,导致通知线程也无法执行monitorY.notifyAll(),因此等待线程会一直等待下去。这种由于嵌套锁导致通知线程始终无法唤醒等待线程的活性障碍被称为嵌套监视器锁死。

线程饥饿

线程饥饿指的是线程一直无法获得其所需的资源而导致任务一直无法进展的一种活性障碍。
产生线程饥饿的一般条件都是非公平锁的使用。死锁也属于线程饥饿,因为死锁也是因为某种情况从而导致无法获取资源。导致线程饥饿的条件仅仅是无法获取资源,比如显示锁(非公平锁)的使用,但这并不意味会导致死锁。

活锁

没有阻塞当前线程申请资源,但申请资源一直未成功,也就是当前线程一直处于做无用功的状态,这是活锁。----屡战屡败,屡败屡战。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/79281.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机-校验码

码距:就单个编码A:00而言&#xff0c;其码距为1&#xff0c;因为其只需要改变一位就变成另一个编码。在两个编码中&#xff0c;从A码到B码转换所需要改变的位数称为码距&#xff0c;如A:00要转换为B:11&#xff0c;码距为2。一般来说&#xff0c;码距越大&#xff0c;越利于纠错…

基于C++实现(控制台)仓库管理系统【100010021】

1题目与要求 1.1问题描述 某电子公司仓库中有若干批次的同一种电脑&#xff0c;按价格、数量来存储。要求实现功能: 初始化 n 批不同价格电脑入库&#xff1b;出库&#xff1a;销售 m 台价格为 p 的电脑&#xff1b;入库&#xff1a;新到 m 台价格为 p 的电脑&#xff1b;盘…

Burp Suite Professional 22.11.4 Crack

Burp Suite Professional 是网络安全测试人员的首选工具包。使用它来自动执行重复的测试任务 - 然后使用其专家设计的手动和半自动安全测试工具进行更深入的挖掘。Burp Suite Professional 可以帮助您测试 OWASP Top 10 漏洞 Burp Suite 被描述为通过 Port Swigger 提供给用户…

Python学习基础笔记三十七——collections模块

1、collections模块&#xff1a; 内置数据类型&#xff1a;列表list、字典dict、集合set、元组tuple。 Collections模块提供了另外的数据类型&#xff1a; 队列deque、双端队列&#xff1a;可以快速地从另外一侧追加和推出元素&#xff1b; namedtuple&#xff1a; 生成可以…

游戏开发53课 阴影

4.8 阴影 阴影的实现方式有很多种&#xff0c;消耗和效果各异。 4.8.1 贴图阴影 贴图的方式最简单&#xff0c;做法是制作一张阴影纹理&#xff0c;放到物体脚下&#xff08;下图&#xff09;&#xff0c;跟随物体一起运动。 贴图阴影渲染非常简单&#xff0c;只需要两个三角…

智能聊天机器人技术研究与应用

文章大纲 1. 聊天机器人简介聊天机器人进化历史聊天机器人核心技术2. 预训练模型与聊天机器人研究进展transfomer 架构回顾预训练对话模型3. 知识图谱与智能问答4. 智能聊天机器人应用实践5. 总结与展望正确使用chatGPT“高端的食材往往只需要最朴素的烹饪方式”参考文献与学习…

OpenFeign使用

OpenFeign使用 在微服务的架构中&#xff0c;传统的http客户端如Httpclient Okhttp HttpURLConnection RestTemplate WebClient 显然不适合。毕竟需要动态的获取服务地址&#xff0c;和进行负载均衡调用。 RPC框架 PC 全称是 Remote Procedure Call &#xff0c;即远程过程调…

NTC-Templates解析与采集H3C(Comware Version 7)信息

本文仅供本人参考与学习NTC-Templates模板使用。 设备环境&#xff1a;HCL S6850 S5820V2-54QS-GE&#xff1b;Version 7.1.075, Alpha 7571 模板采用&#xff1a;hp_comware_display_xxxxxxxx.textfsm 在线模板测试&#xff1a;https://textfsm.nornir.tech/ hp_comware_d…

httpbin的使用

在学习过程中我们想知道我们发送处的http格式是什么样子的&#xff0c;是否符合我们的要求&#xff0c;寻找一个这样的工具&#xff0c;满足验证测试需要。 Httpbin服务可以满足查看我发出去的请求到底是什么样子的。你需要查看请求中的那部分信息&#xff0c;就调用什么样的接…

【Qt入门第37篇】 网络(七)TCP(一)

导语 TCP即TransmissionControl Protocol&#xff0c;传输控制协议。与UDP不同&#xff0c;它是面向连接和数据流的可靠传输协议。也就是说&#xff0c;它能使一台计算机上的数据无差错的发往网络上的其他计算机&#xff0c;所以当要传输大量数据时&#xff0c;我们选用TCP协议…

游戏开发45课 性能优化4

2.6 粒子 粒子特效也是性能的一个大杀手&#xff0c;主要体现在&#xff1a; 每帧计算量大。涉及发射器/效果器/曲线插值等&#xff0c;耗费CPU性能。频繁操作内存。粒子在生命周期里&#xff0c;实例持续不断地创建/删除&#xff0c;即便有缓存机制下&#xff0c;依然避免不…

算法day43|1049,494,474

1049. 最后一块石头的重量 II class Solution:def lastStoneWeightII(self, stones: List[int]) -> int:summ sum(stones)target summ//2#dp下标和数组的定义,dp[j]代表的是最大价值dp [0]*15001#递归公式for i in range(len(stones)):for j in range(target,stones[i]-…

Zero-Shot Learning across Heterogeneous Overlapping Domains

极简论文阅读 摘要 a zero-shot learning approach:零样本学习方法。 natural language understanding domain&#xff1a;自然语言处理域。 a given utterance&#xff1a;给定的话语。 domains at runtime&#xff1a;运行时的域。 utterances and domains 给定话语和域。 …

Yolact

YOLACT 1.Abstract 原理: 生成一组 prototype masks (原型掩码) 个数&#xff08;nm&#xff09;可自定义&#xff0c;基于protonet的最后一组卷积核个数 通过一组 coefficients (掩码系数) 预测每个 instance mask (输出mask) 掩码系数由head层输出&#xff0c;shape为&…

微服务框架 SpringCloud微服务架构 微服务保护 32 隔离和降级 32.2 线程隔离

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 微服务保护 文章目录微服务框架微服务保护32 隔离和降级32.2 线程隔离32.2.1 线程隔离32.2.2 优缺点对比32 隔离和降级 32.2 线程隔离 32.…

算法7:平衡二叉树(AVLTree)

二叉排序树&#xff08;BST, Binary Sort Tree&#xff09;也称二叉查找树&#xff08;Binary Search Tree&#xff09;, 或二叉搜索树。 定义&#xff1a;一颗二叉树&#xff0c;满足以下属性&#xff1a; 左子树的所有的值小于根节点的值右子树的所有值大于根节点的值左、右…

关键词(四)

关键词&#xff08;四&#xff09;一.具有争议的关键词—goto二.“空”的关键字—void1.void为什么不能定义变量2.void修饰函数返回值和参数3.void指针一.具有争议的关键词—goto goto语句非常灵活&#xff0c;其实就是从goto这个位置直接跳转到goto后面的那个数据&#xff08;…

单例模式、工厂模式

单例模式、一、单例模式1.1 饿汉模式1.1.1 实现1.1.2 补充1.2 懒汉模式1.2.1 单线程版1.2.2 多线程版二、工厂模式一、单例模式 单例模式是校招中最常考的设计模式之一。 啥是设计模式&#xff1f; 设计模式好比象棋中的"棋谱"&#xff1a;红方当头炮&#xff0c;黑…

软件测试人员究竟要掌握什么技能?顺便说下行业现状

最近团队内部产品在做性能测试中碰到一个问题&#xff0c;不仅仅这次性能测试&#xff0c;其实这在我这近10年工作过程中&#xff0c;经常碰到一些类似的事情&#xff0c;今天拿出来一件事说叨说叨。 1、事情经过 月中上线了一个功能&#xff0c;该功能会应对峰值流量&#x…

【安卓APP源码和设计报告(含PPT)——订餐系统

订餐系统实验报告 课程名称&#xff1a; Android程序设计 班 级&#xff1a; 学 号&#xff1a; 姓 名&#xff1a; 任课教师&#xff1a; 程序功能与环境&#xff08;服务器&#xff0c;手机实物照片&#xff0c;自己拍&#xff09; 程序功能 餐厅订餐系统服务器&#…