【JUC】四、可重入锁、公平锁、非公平锁、死锁现象

文章目录

  • 1、synchronized
  • 2、公平锁和非公平锁
  • 3、可重入锁
  • 4、死锁

1、synchronized

写个demo,具体演示下对象锁与类锁,以及synchronized同步下的几种情况练习分析。demo里有资源类手机Phone,其有三个方法,发短信和发邮件这两个方法有synchronized关键字,另一个普通方法getHello。然后启动两个线程AA和BB,且二者进入就绪状态中间休眠100ms,给AA一个先抢夺CPU时间片的优势。

public class Lock8 {

    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"AA").start();

        /**
         * start后线程进入的是就绪状态,即具有抢夺CPU时间片(执行权)的能力,并不是直接执行
         * 这里刻意休眠100毫秒,让AA线程先去抢时间片,给AA一个先执行的优势
         */
        Thread.sleep(100);

        new Thread(() -> {
            try {
                phone.sendEmail();
                //phone.getHello();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"BB").start();

    }

}
class Phone {
    public synchronized void sendSMS() throws Exception {
        //TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    }

    public synchronized void sendEmail() throws Exception {
        System.out.println("------sendEmail");
    }

    public void getHello() {
        System.out.println("------getHello");
    }
}

分析以下八种情况的输出结果:

Case1:就上面的代码,直接执行

Case2:sendSMS()方法体加一行TimeUnit.SECONDS.sleep(4),即停留4秒

分析:对于上面两种情况,synchronized出现在示例方法中占的是对象锁,而两线程共用同一个对象,因此先抢时间片的线程AA先执行,而sleep是抱着锁睡,所以输出都是:

------sendSMS
------sendEmail

Case3:BB线程改为调用普通方法getHello

分析:getHello方法不用对象锁,所以不用等,而AA线程的sendSMS要sleep4秒,因此getHello就先输出:

------getHello
------sendSMS

Case4:两个手机Phone对象,分别给AA和BB线程调用两个synchronized方法

分析:两个Phone对象,两个对象锁,各自调synchronized实例方法,没有抢锁和等待的情况,没有sleep的自然先输出:

------sendEmail
------sendSMS

Case5:两个synchronized方法均加static改为静态方法,两线程共用1个Phone资源对象

Case6:两个synchronized方法均加static改为静态方法,两线程分别用2个Phone资源对象

分析:synchronized两个静态方法,锁的就是类锁,即当前类的Class对象,一个类就一把类锁,所以尽管有两个Phone对象在调也没用,先拿到类锁的先执行并输出:

------sendSMS
------sendEmail

Case7:BB线程调用静态同步sendEmail、AA线程调用无static的同步方法sendSMS,两线程共用1个Phone资源对象

Case8:BB线程调用静态同步sendEmail、AA线程调用无static的同步方法sendSMS,两线程分别用2个Phone资源对象

分析:不管1个/2个Phone对象,AA线程用的对象锁,BB线程用的类锁,互不影响,对象锁代码中有sleep,晚输出:

------sendEmail
------sendSMS

总结:

synchronized实现同步时:

  • synchronized加在静态方法上,锁的就是类锁,即当前类的Class对象,一个类就一把类锁
  • synchronized加在实例方法上,锁的是调用该方法的当前对象,是对象锁,一个类创建100个对象,就有100把对象锁
  • synchronized加在代码块上,锁的是synchronized括号里配置的对象

2、公平锁和非公平锁

还是之前三个线程卖30张票的例子:

//资源类
class LTicket{

    private Integer number = 30;

    private final ReentrantLock lock = new ReentrantLock();

    public void sale(){
        //上锁
        lock.lock();
        try {
            if( number > 0 ){
                System.out.println(Thread.currentThread().getName() + ": 卖出票,剩余" + number--);
            }

        } finally {
            //释放锁写finally语句中,防止上面发生异常导致锁未释放
            lock.unlock();
        }
    }
}

开启三个线程,调用资源类的方法:

public class LSaleTicket {

    public static void main(String[] args) {
        LTicket ticket = new LTicket();
        new Thread(() -> {
            for(int i = 0 ; i < 40; i++ ){
                ticket.sale();
            }
        },"AA").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0 ; i < 40; i++ ){
                    ticket.sale();
                }
            }
        },"BB").start();
        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "CC").start();
    }
}


执行会发现,经常AA一个线程就把所有票一个人卖完了:

在这里插入图片描述

这就是非公平锁,上面new Lock对象时:

private final ReentrantLock lock = new ReentrantLock();
//不传参数,默认非公平锁

源码:

在这里插入图片描述

改为公平锁:

private final ReentrantLock lock = new ReentrantLock(true);

看下效果:

在这里插入图片描述

总结:

  • 非公平锁:可能导致线程饿死,但效率高
  • 公平锁:阳光普照,但效率比非公平锁低

二者相比,就像去图书馆自习,非公平锁是看到座位就座,公平锁则是先问下这里有人吗,如果有,就排队,公平锁的源码:

在这里插入图片描述

3、可重入锁

synchronized( 隐式)和 Lock( 显式)都是可重入锁,这里的显式隐式即指的Lock需要开发者手动加锁解锁。可重入锁,理解为你家大门上有锁、卧室门上有锁、卫生间有锁,但只要你打开了大门的锁,卧室、卫生间就不需要再次开锁了,这些房间就可以自由进入了。类比到代码中,就是他们用的是同一把锁。可重入锁又叫递归锁。

在这里插入图片描述
写个synchronized同步代码块

public class SyncLockDemo {
    
    public static void main(String[] args) {
        
        Object o = new Object();
        new Thread(() -> {
            synchronized (o){
                System.out.println(Thread.currentThread().getName() + " 外层");
                synchronized (o){
                    System.out.println(Thread.currentThread().getName() + " 中层");
                    synchronized (o){
                        System.out.println(Thread.currentThread().getName() + " 内层");
                    }
                }
            }
        },"t1").start();

    }
}

在这里插入图片描述

关于可重入锁又叫递归锁:

public class SyncLockDemo {
    
    public synchronized void add(){
        add();
    }

    public static void main(String[] args) {
        
        new SyncLockDemo().add();
     
    }
}

在这里插入图片描述

可以看到递归调用synchronized实例方法add,会出现StackOverflowError,就可以说明可重入锁的特点,要是不可重入,那递归时再调add方法,就没对象锁给它用了。再用lock显示演示:

在这里插入图片描述

另外,lock与unlock必须成对,当然这里内层锁的unlock注释掉,也能运行成功,进入大门后你是可以自由活动的,但你少个unlock,后面线程再想lock,就等不到锁了,相当于你进门,自己休息好了再出来却不带钥匙就把门关了。

4、死锁

在这里插入图片描述
t1线程执行某同步代码块,用到了对象1的锁和对象2的锁,即t1线程需要先锁对象1,再锁对象2,全锁以后,算同步代码块执行结束,然后一下释放两个对象锁。(A加锁-B加锁-B解锁-A解锁)

t2线程执行另一个同步代码块,需要先锁对象2,再锁对象1才算这个同步代码块执行结束,然后释放两个对象锁。(B加锁-A加锁-A解锁-B解锁)

如此:t1锁到对象2的时候,发现已被锁,则等待,而另一边:t2锁到对象1的时候,发现对象1已被锁,两个线程同时陷入无休止的等待…尬住了。此时,若无外力干涉,就执行不下去了,表现在执行结果就是光标闪烁,无输出,但也没有执行结束。

产生死锁的原因:
  • 系统资源不足
  • 进程运行推进顺序不合理
  • 资源分配不当

写个死锁的Demo:

public class DeadLock {

    public static void main(String[] args) {
        Object o1= new Object();
        Object o2= new Object();

        new Thread(() -> {
            synchronized (o1){
                System.out.println(Thread.currentThread().getName()  + "===>持有o1对象锁,试图获取o2对象锁");
                try {
                    Thread.sleep(200);  //抱着o1锁睡会儿,别太快执行结束释放两个锁,以保证死锁必现
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName()  + "===>获取到o2对象锁");
                }
            }
        },"t1").start();

        new Thread(() -> {
            synchronized (o2){
                System.out.println(Thread.currentThread().getName()  + "===>持有o2对象锁,试图获取o1对象锁");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName()  + "===>获取到o1对象锁");
                }
            }
        },"t2").start();
    }

}

运行:

在这里插入图片描述

当然,不能一看到不exit0,也无输出就说是死锁,死循环、远程调用也可能有这个情况,关于是否是死锁的验证:

  • jps:类似Linux的ps -ef
  • jstack:JVM自带堆栈跟踪工具

关于jps:
在这里插入图片描述
没配环境变量,在IDEA终端先cd到这儿,再执行:

在这里插入图片描述

然后在IDEA终端继续执行:

jstack 10212

返回的关键信息:

在这里插入图片描述

关于死锁和线程安全的优化:

synchronized会让程序执行效率变低,系统吞吐量降低,用户体验变差。解决线程安全,可考虑:

  • 使用局部变量代替实例变量和静态变量
  • 若必须使用实例变量,考虑多创建几个对象,别对象共享了也就没有安全问题了
  • 若以上两条都做不到,则用synchronized

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RXMVB2 2RK251 206AN大容量双位置继电器 JOSEF约瑟 DC110V

系列型号&#xff1a; RXMVB2 RK 251 204大容量双位置继电器; RXMVB2 RK 251 205大容量双位置继电器; RXMVB2 RK 251 206大容量双位置继电器; DCS-11大容量双位置继电器; DCS-12大容量双位置继电器; DCS-13大容量双位置继电器; 一、用途 RXMVB2(DCS-10)系列大容量双位置继电器…

【开源】基于Vue和SpringBoot的校园失物招领管理系统

项目编号&#xff1a; S 006 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S006&#xff0c;文末获取源码。} 项目编号&#xff1a;S006&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 招领管理模块2.2 寻物管理模块2.3 系…

HackTheBox-Starting Point--Tier 2---Vaccine

文章目录 一 Vaccine 测试过程1.1 打点1.1.1 FTP匿名登录1.1.2 SQL注入 1.2 权限提升 二 题目 一 Vaccine 测试过程 1.1 打点 1.端口扫描 nmap -sV -sC 10.129.191.631.1.1 FTP匿名登录 2.FTP允许匿名登录&#xff0c;发现backup.zip ftp 10.129.191.63解压backup.zip&#x…

Docker-minio部署

1.创建目录 创建文件目录&#xff0c;用来存放配置和上传文件目录 &#xff08;1&#xff09;Minio 外部挂载的配置文件(/mydata/minio/config) &#xff08;2&#xff09;存储上传文件的目录(/mydata/minio/data) mkdir -p /home/minio/config mkdir -p /home/minio/data2.拉…

黑群晖断电导致存储空间已损毁修复记录

黑群晖断电2次,担心的事情还是发生了,登录后提示存储空间已损毁...... 开干!! 修复方式: 1.使用SSH登录到群晖,查看相关信息 # 登录后先获取最高权限 root@DiskStation:~# sudo -i # 检测存储池状态 root@DiskStation:~# cat /proc/mdstat Personalities : [linear] […

【JavaEE】Servlet API 详解(HttpServletRequest类)

二、HttpServletRequest Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并且按照 HTTP 协议的格式把字符串解析成 HttpServletRequest 对象&#xff08;内容和HTTP请求报文一样&#xff09; 1.1 HttpServletRequest核心方法 1.2 方法演示 WebServlet("/showRequest&…

M2LC-Net

模型结构 作者未提供代码

Eclipse使用配置tomcat服务:未识别的web项目

问题1&#xff1a;未识别的项目 解决&#xff1a;elispse未识别到改项目为Web项目

DDD设计模式需要在存储层之前就需要有ID,如何实现?

在DDD设计领域中, 聚合根 或者实体在存储层之前就需要有id。一般采用如下类提前生成,然后直接落库。 DDD元素 在使用DDD设计系统时,主要包括Entity,Value Object,Service,Aggregate,Repository,Factory,Domain Event,Moudle等元素 在建模时,Entity可以用来代表一个事物…

【MySQL】事务(下)

文章目录 1. 各个隔离级别的演示事务隔离级别 —— 读未提交事务隔离级别—— 读提交事务隔离级别 —— 可重复读事务隔离级别 —— 串行化脏读 不可重复读 幻读的理解 2. MVCC机制读写3个记录隐藏列字段undo日志模拟MVCCread view 理论 3. 读提交与 可重复读的区别两者本质区别…

HarmonyOS分布式文件系统开发指导

分布式文件系统概述 分布式文件系统&#xff08;hmdfs&#xff0c;HarmonyOS Distributed File System&#xff09;提供跨设备的文件访问能力&#xff0c;适用于如下场景&#xff1a; 两台设备组网&#xff0c;用户可以利用一台设备上的编辑软件编辑另外一台设备上的文档。平板…

AI大模型的制作:RAG和向量数据库,分别是什么?

目录 一、什么是 AI 大模型 二、RAG 三、向量数据库 四、如何制作一个好的 AI 大模型 一、什么是 AI 大模型 AI大模型是指具有大规模参数和复杂结构的人工智能模型。传统的机器学习模型通常有限的参数量&#xff0c;而AI大模型则通过增加参数量和层数来提升模型的表达能力…

黑客泄露 3500 万条 LinkedIn 用户记录

被抓取的 LinkedIn 数据库分为两部分泄露&#xff1a;一部分包含 500 万条用户记录&#xff0c;第二部分包含 3500 万条记录。 LinkedIn 数据库保存了超过 3500 万用户的个人信息&#xff0c;被化名 USDoD 的黑客泄露。 该数据库在臭名昭著的网络犯罪和黑客平台 Breach Forum…

经纬恒润马来西亚工厂正式投入试运行

2023年11月&#xff0c;经纬恒润在中国境外的第一家工厂正式投入试运行。新工厂位于马来西亚&#xff0c;于2023年4月开始筹建&#xff0c;规划总产能500万个汽车电子控制器&#xff0c;主要用于生产新能源汽车电子产品&#xff0c;以满足国外客户日益增长的需求。 经纬恒润马来…

C语言从入门到精通之【字符串】

C语言没有专门用于储存字符串的变量类型&#xff0c;字符串都被储存在char类型的数组中。数组由连续的存储单元组成&#xff0c;字符串中的字符被储存在相邻的存储单元中&#xff0c;每个单元储存一个字符&#xff0c;每个字符占1个字节。 数组末尾位置的字符\0。这是空字符&am…

Eclipse使用配置tomcat服务:部署找不到web.xml

问题&#xff1a;部署找不到web.xml及其他资源文件。只有lib和class 解决&#xff1a;将web.xml所在目录添加到部署配置里

设备数据如何为预测性维护提供支持

预测性维护是现代制造业中一种高效而受欢迎的维护策略&#xff0c;它能够帮助企业提前发现设备故障的早期迹象&#xff0c;并采取相应措施&#xff0c;从而避免生产线的停机和生产效率的下降。实施预测性维护的关键在于充分利用设备数据&#xff0c;通过数据的收集、处理和分析…

yolo如何画框、如何变换目标检测框的颜色和粗细、如何运行detect脚本

这段代码是一个使用YOLO模型进行目标检测的Python脚本。下面我将逐步解释脚本的主要部分&#xff0c;并提供一些关于超参数的使用方法。 1. 脚本结构 导入相关库设置配置参数加载YOLO模型运行目标检测处理检测结果显示或保存结果 2. 超参数说明 --weights: 指定YOLO模型的…

工业控制(ICS)学习笔记

目标&#xff1a;工业互联网安全的比赛 工控CTF之协议分析1——Modbus_ctf modbus-CSDN博客 常见的工控协议有&#xff1a;Modbus、MMS、IEC60870、MQTT、CoAP、COTP、IEC104、IEC61850、S7comm、OMRON等 不用看了&#xff0c;没太多技术含量&#xff0c;做了一会发现全得看答案…