多把锁,死锁,活锁,饥饿

news2024/11/19 18:31:22

目录

多把锁

多把锁的优缺点

活跃性

死锁

手写死锁

死锁的四个必要条件

定位死锁

jconsole运行命令

jps 定位进程 id,再用 jstack 定位死锁

死锁的三种场景

一个线程一把锁

两个线程两把锁

多个线程多把锁

解决死锁

活锁

饥饿


多把锁

现在有一个场景,有一个大屋子,小南想去睡觉,小女想去学习,两个人做的事毫不相关,如果他们同时想去用一间屋子的话,那么并发度就会很低

我们看一下代码 :

@Slf4j(topic = "c.BigRoom")
public class BigRoom {
    public void studying() {
        synchronized (this) {//对这个大房间加锁
            log.debug("study2000ms....");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void sleeping() {
        synchronized (this) {//对这个大房间加锁
            log.debug("sleeping1000ms....");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class TestMain {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(()->{
            bigRoom.studying();
        },"小南").start();
        new Thread(()->{
            bigRoom.sleeping();
        }).start();
    }
}

也就是说他们做的事情都要对同一个房间加锁,也就是只有一个人用完了这个房间,另一个人才能用.

这和我们以前两个线程同时访问一个共享的东西时一样的.

但是现在小南和小女一个要去学习,一个要取睡觉,两个人做的事情毫不相干,就可以去大房子的不同房间去做事.

也就是我们现在使用多把锁->当多个线程干的事情毫不相干就可以使用多把锁.

@Slf4j(topic = "c.BigRoom")
public class BigRoom {
    private static final Object studyingRoom = new Object();
    private static final Object sleepingRoom = new Object();

    public void studying() {
        synchronized (studyingRoom) {
            log.debug("study2000ms....");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void sleeping() {
        synchronized (sleepingRoom) {
            log.debug("sleeping1000ms....");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class TestMain {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(()->{
            bigRoom.studying();
        },"小南").start();
        new Thread(()->{
            bigRoom.sleeping();
        },"小女").start();
    }
}

我们这时候改了一下代码,小南要去学习就去学习房间,小女要去睡觉就去睡觉房间,两者干的事情毫不相关

这时候,就会提高并发度,让锁的粒度变得更细.

多把锁的优缺点

  • 优点 : 可以提高并发度,让锁的粒度变得更细.
  • 缺点 : 如果一个线程同时要获得多把锁,容易发生死锁.

活跃性

死锁

当一个线程同时要获取多把锁,这个时候就容易发生死锁.

就比如 t1线程获得A对象的锁,t2线程获得了B对象的锁,

而此时t1线程又想获取B对象的锁,等待t2线程释放B对象的锁,t2线程又想获取A对象的锁,等待t1线程释放A对象的锁.

也就是双方各自有一把锁,还想获取到对方的锁,这就是死锁现象

手写死锁

@Slf4j(topic = "c.DeadLock")
public class DeadLock {

    private static final Object A = new Object();
    private static final Object B = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                log.debug("lock A");
                //线程t1获取了A对象的锁,1s之后,又要获取B对象的锁
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B) {
                    log.debug("lockB");
                    log.debug("操作...");
                }
            }
        },"t1");

        Thread t2 = new Thread(() -> {
            synchronized (B) {
                log.debug("lock B");
                //线程t2获取了B对象的锁,1s之后,又要获取A对象的锁
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A) {
                    log.debug("lock A");
                    log.debug("操作...");
                }
            }
        },"t2");
        t1.start();
        t2.start();
    }
}

死锁的四个必要条件

  • 互斥条件:该资源任意一个时刻只由一个线程占用。
  • 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件: 线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源
  • 循环等待条件: 若干线程之间形成一种头尾相接的循环等待资源关系

定位死锁

发生了死锁现象我们怎么来定位死锁呢 ?

我们最经常使用的是jconsole命令工具检测死锁, 也可以使用使用 jps 定位进程 id,再用 jstack 定位死锁

jconsole运行命令

比如我运行刚才死锁的程序.然后利用jconsole来检测死锁.

点击连接.

t1线程也同理

从这里可以看见想获取的锁的持有者,还能看见具体发生死锁现象的代码行数,然后对其进行分析修改打破死锁现象即可.

jps 定位进程 id,再用 jstack 定位死锁

同样也可以从这里可以看见想获取的锁的持有者,还能看见具体发生死锁现象的代码行数,然后对其进行分析修改打破死锁现象即可.

死锁的三种场景

一个线程一把锁

如果是不可重入锁,一个线程加锁两次,就会导致第一个锁的释放依赖第二个锁加锁成功,而第二个加锁成功又依赖第一个锁的释放,导致死锁.

两个线程两把锁

线程A持有资源1,线程B持有资源2,两个线程又同时想得到对方的资源,导致死锁

多个线程多把锁

哲学家就餐问题

最经典的导致死锁的问题,那就是哲学家就餐问题.

哲学家就餐问题说的就是有5位哲学家(相当于5个线程)正在吃饭,但是只有五根筷子(筷子就相当于这5个线程所共享的资源),每位哲学家左边有一个筷子,右边有一根筷子,哲学家只有拿到左边筷子有拿到右边筷子才能吃饭,如果筷子被别的哲学家拿到了,自己只能等待那个哲学家吃完,自己才能吃.

很容易发生死锁的现象,就是五个哲学家同时各拿一根筷子,当哲学家同时又要拿另一根筷子的时候,就会发生死锁,也就是自己持有了一个资源,想获取另一个资源而又等待对方去释放资源.

这样5个哲学家就构成了循环等待的这种关系.

代码实现

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Philosophers")
public class Philosophers extends Thread{
    private Chopstick left;//左手筷子
    private Chopstick right;//右手筷子

    public Philosophers(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while(true) {
            synchronized (left) { //先拿左手筷子
                synchronized (right) { //在拿右手筷子
                    eat();//然后吃饭
                }
            }
        }
    }

    private void eat() {
        log.debug("eating...");
        try {
            Thread.sleep(1000);//思考1s钟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Chopstick {
    private String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Chopstick{" +
                "name='" + name + '\'' +
                '}';
    }
}
public class Main {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosophers("苏格拉底",c1,c2).start();
        new Philosophers("柏拉图",c2,c3).start();
        new Philosophers("亚里士多德",c3,c4).start();
        new Philosophers("赫拉克利特",c4,c5).start();
        new Philosophers("阿基米德",c5,c1).start();
     }
}

这就会导致死锁,五位哲学家构成了循环等待的这种关系

要想打破死锁,可以使用ReentrantLock,或者打破死锁的必要条件之一,最重要的是循环等待必要条件

解决死锁

我们首先看一下产生死锁的必要条件 :

  • 互斥条件:该资源任意一个时刻只由一个线程占用。
  • 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  • 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系

破坏死锁的产生的必要条件即可:

  1. 破坏请求与保持条件 :一次性申请所有的资源。
  2. 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  3. 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放.(最最重要)

最最重要的就是破坏循环等待条件,

针对每一把锁进行编号,约定在获取多把锁的时候,明确获取锁的顺序(升序还是降序都可以),所有线程都遵守这样的顺序,同一顺序拿到锁,就会反序释放锁,就会打破循环等待

我们按照顺序在执行一下哲学家就餐问题.

每一个哲学家左右有两个筷子,我们按顺序规定从小到大的顺序来拿筷子(比如1和2先拿1号,2和3先拿2),最后,只有一个哲学家同时拿到两根筷子,等到它吃完了,然后在放下筷子,别的哲学家就可以吃,从小到大的拿取筷子,最后就从大到小的放下筷子.

我们分析一下上面的代码为什么避免了死锁的发生?

我们按照一定顺序加锁后当线程1获得了resource1的监事锁,线程2就获取不到了.然后线程1再去获取resource2的监事锁,可以获取到,然后线程1释放了对resource1,resource2的监事锁的占用,线程2获取到就可以执行了,这样就破坏了循环等待条件.

对于代码层面,我们还可以使用ReentrantLock来解决

活锁

活锁 : 两个线程互相改变对方的结束条件,最终谁也没有结束

解决活锁: 执行时间有一定的交错-->让睡眠的时间是随机数

能让其交错开,第一个线程马上要运行完了,第二个线程就没有机会改变对方的结束条件了

import lombok.extern.slf4j.Slf4j;

/**
 * 活锁 : 两个线程互相改变对方的结束条件,最终谁也没有结束
 * 解决活锁: 执行时间有一定的交错-->让睡眠的时间是随机数
 * 能让其交错开,第一个线程马上要运行完了,第二个线程就没有机会改变对方的结束条件了
 */
@Slf4j(topic = "c.TestLiveLock")
public class TestLiveLock {
    private static int count = 10;

    public static void main(String[] args) {
        /**
         * t1线程想将count=10减到0
         */
        new Thread(() -> {
             while (count>0) {
                 try {
                     Thread.sleep(200);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 count--;
                 log.debug("count : {}",count);
             }
        },"t1").start();
        /**
         * t2线程想将count=10减到0
         */
        new Thread(() -> {
            while (count<20) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++;
                log.debug("count : {}",count);
            }
        },"t2").start();
    }
}

解决活锁: 执行时间有一定的交错-->让睡眠的时间是随机数

能让其交错开,第一个线程马上要运行完了,第二个线程就没有机会改变对方的结束条件了

饥饿

一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束

还是拿刚才哲学家就餐问题:

虽然打破了死锁的局面,但是发现有的线程执行次数太少,都被其他线程抢去锁了,这就是一种饥饿现象.(拿不到筷子吃不上饭了,都被别人抢去了)

可以通过ReentrantLock来解决.

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

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

相关文章

Kali Linux ping扫描命令

1.命令介绍 kali的ping命令和centos linux的评命令许多参数都差不多&#xff0c;可以看一下我之前写的linuxping命令https://blog.csdn.net/qq_44652591/article/details/128439494 ping扫描是ping整个网络IP地址或单个IP&#xff0c;以查明它们是否活跃和响应的过程。ping也是…

用Kettle调用Restful API 接口

1 概述 kettle 中文名称叫水壶&#xff0c;是纯 java 开发&#xff0c;开源的 ETL工具&#xff0c;用于数据库间的数据迁移 。可以在 Linux、windows、unix 中运行。有图形界面&#xff0c;也有命令脚本还可以二次开发。当然它也可以用来调用Restful API 来采集数据&#xff0c…

【安全硬件】Chap.6 IC和半导体产业的全球化;芯片生产猜疑链与SoC设计流程;可能会存在的安全威胁: 硬件木马、IP盗版、逆向工程、侧信道攻击、伪造

【安全硬件】Chap.6 IC和半导体产业的全球化&#xff1b;芯片生产猜疑链与SoC设计流程&#xff1b;可能会存在的安全威胁: 硬件木马、IP盗版、逆向工程、侧信道攻击、伪造背景1. IC和半导体产业的全球化2. 芯片生产猜疑链——Untrusted IC Supply Chain Threats可能会存在的安全…

我只是把握好了这3点,1个月后成功拿下大厂offer!

目录 一、写在前面二、技术广度的快速准备三、技术深度的快速准备四、基础功底的快速准备五、下篇预告 一、写在前面 春节过后&#xff0c;即将迎来的是一年一度的金三银四跳槽季。 假如你准备在金三银四跳槽的话&#xff0c;那么作为一个Java工程师&#xff0c;应该如何利…

【Shell】mksh运行分析

mksh运行分析 Shell shell&#xff0c;壳子&#xff0c;即操作系统的壳子。这层壳子套在操作系统上&#xff0c;为用户提供与操作系统的交互手段。 操作系统的交互方式一般有&#xff0c;图形化交互(GUI)和命令行交付(CLI,command-line interface)。 套在操作系统上的壳子 …

虚拟机栈

虚拟机栈简介虚拟机栈的出现背景内存中的栈与堆虚拟机栈基本内容虚拟机栈的特点虚拟机栈的异常设置栈内存大小栈的存储单位栈中存储什么&#xff1f;栈运行原理栈帧的内部结构局部变量表认识局部变量表关于Slot的理解Slot代码示例Slot的重复利用静态变量与局部变量的对比补充说…

oracle安装教程

1安装和创建用户 1.1.安装以及常见问题 oracle安装教程 安装中 system密码改成root oracle卸载&#xff0c;除此之外清除C:\Program Files内的oracle 需要准备oracle安装包和plsql界面化操作工具 重装报错bug&#xff1a; plsql操作 plsql注册码 product code: ke4tv8t5jtxz…

java动态代理-面向切面代码样例

1.测试入口import java.lang.reflect.Proxy;/*** 面向切面测试* author epsoft-hy**/ public class test {public static void main(String[] args) {Class<?>[] cls {UserDao.class};//接口一个String classpath"util.aop.UserDaoImp";//访问类路径test2(tes…

【学vue跟玩一样】快速学会常用指令及如何理解生命周期

一&#xff0c;内置指令1.v-textv-bind:&#xff0c;单向绑定解析表达式v-model :双向数据绑定v-for:遍历数组/对象/字符串v-on :绑定事件监听,可简写为v-if :条件渲染(动态控制节点是否存存在)v-else :条件渲染(动态控制节点是否存存在)v-show:条件渲染(动态控制节点是否展示)…

3. Rstudio【可视化导入】数据

b站课程视频链接&#xff1a;https://www.bilibili.com/video/BV19x411X7C6?p1 腾讯课堂(最新&#xff0c;但是要花钱&#xff0c;我花99元买了&#xff0c;感觉不错&#xff09;&#xff1a;https://ke.qq.com/course/3707827#term_id103855009 &nbsp&#xff1b; 本笔记…

SigmaStar空板烧录

一、硬件连接 在官方给的SDK文档中&#xff0c;有描述如何使用ISP TOOL给空板烧录程序&#xff0c;但坑的是&#xff0c;文档中并没有写这个上位机工具需要配合Mstar专门的烧录工具来使用&#xff0c;烧录工具如下图所示。 上图中有三根连接线&#xff0c;示意图如下。目标板的…

HTML与CSS基础(九)—— 综合项目(CSS样式补充、项目前置认知、项目结构搭建)

目标能够在网页中使用 精灵图 能够使用 背景大小属性 &#xff0c;设置背景图片的大小 能够认识 CSS书写顺序&#xff0c;提高代码专业性和浏览器渲染性能 能够使用的专业方式完成 项目结构搭建 和 基础公共样式 能够应用已学技术知识 完成小兔鲜儿项目一、项目样式补充目标&am…

URL中绝对路径与相对路径拼接问题总结

URL中绝对路径与相对路径拼接问题总结1. 基础环境2. 测试2.1 总结2.2 本地文件url1. 基础环境 这里用nginx的docker镜像构建了一个服务&#xff0c;步骤如下&#xff1a; sudo docker pull nginxsudo docker run -d --name nginx01 -p 8083:80 nginx nginx发布的根目录是/us…

Redis安装及常用数据类型介绍

1、redis介绍1.1、redis是一个开源的key-value存储系统1.2、和Memcached类似&#xff0c;它支持存储的value类型相对很多&#xff0c;包括String、list、set、zset以及hash类型。1.3、这些数据类型都支持push/pop、add/remove及取交集并集及更丰富的操作&#xff0c;而且这些操…

LeetCode[264]丑数II

难度&#xff1a;中等题目&#xff1a;给你一个整数 n&#xff0c;请你找出并返回第 n个 丑数 。丑数 就是只包含质因数 2、3和/或 5的正整数。示例 1&#xff1a;输入&#xff1a;n 10输出&#xff1a;12解释&#xff1a;[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组…

【NKOJ三校 初中新年大联欢DIV2】考试游记

目录 看不见的手ljj的方块零它来了宝石转换ljj的距离零它走了彩蛋 题目 【A NKOJ-P8629】 看不见的手 40pts 考试思路&#xff1a; 这道题一看数据范围就知道是O(n)O(n)O(n)的结论题&#xff0c;考试的时候脑子抽筋偏分输出1得了40pts&#xff1b; 正解&#xff1a; 他说…

VISTA -MIT开源基于数据驱动的自动驾驶仿真引擎

引言 VISTA 是MIT开源的一个基于数据驱动的用于自动驾驶感知和控制的仿真引擎。VISTA API提供了一个接口&#xff0c;用于将真实世界的数据集转换为具有dynamic agents、sensor suites、task objectives的虚拟环境。 用过 Unreal Engine 或者 Gazebo的仿真引擎的同学都知道&…

【Kafka】八股文梳理

什么是消息中间件&#xff1f; 消息中间件是基于队列与消息传递技术&#xff0c;在网络环境中为应用系统提供同步或异步、可靠的消息传输的支撑性软件系统。 消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流&#xff0c;并基于数据通信来进行分布式系统的集成。…

Android Native开发系列之C/C++代码调试

Android Native开发系列之C/C代码调试 引言 在做Android native层开发的时候&#xff0c;需要经常调试C/C代码&#xff0c;相较而言通过打日志的方式太不方便了。有两种方式可以进行底层代码的调试。 利用Android studio自带的Debugger进行调试。利用LLDB VSCode进行代码调试…

SAP灵活工作流条件增强

前置 灵活工作流模板中可以设置条件&#xff0c;用来在场景配置中判断是否启动流程或者是否执行该节点 除了流程模板中设置&#xff0c;也可以通过增强设置更为灵活的条件判断,对应增强点SWF_PROCESS_WORKFLOW_CONDITION 增强实施步骤参考 创建增强点实施 复制示例类 为增强设…