【JUC2022】第二章 多线程锁
【JUC2022】第二章 多线程锁
文章目录
- 【JUC2022】第二章 多线程锁
- 一、乐观锁与悲观锁
- 1.悲观锁
- 2.乐观锁
- 二、八锁案例
- 1.标准情况,有a、b两个线程,请问先打印邮件还是短信【结果:邮件】
- 2.sendEmail方法中加入暂停3秒钟,请问先打印邮件还是短信【结果:邮件】
- 3.添加一个普通的hello方法,替换掉线程b中的sendSMS方法,请问先打印邮件还是hello【结果:hello】
- 4.有两部手机,请问先打印邮件还是短信【结果:短信】
- 5.将sendEmail和sendSMS修改为静态同步方法,有1部手机,请问先打印邮件还是短信【结果:邮件】
- 6.sendEmail和sendSMS为静态同步方法,有2部手机,请问先打印邮件还是短信【结果:邮件】
- 7.有1个静态同步方法,有1个普通同步方法,有1部手机,请问先打印邮件还是短信【结果:短信】
- 8.有1个静态同步方法,有1个普通同步方法,有2部手机,请问先打印邮件还是短信【结果:短信】
- 三、synchronized
- 1.字节码分析
- 2.底层原语
- 四、公平锁和非公平锁
- 五、可重入锁
- 六、死锁
- 1.是什么
- 2.手写死锁
- 3.死锁排查命令
一、乐观锁与悲观锁
1.悲观锁
认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改
synchronized关键字和Lock的实现类都是悲观锁
2.乐观锁
认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁
在Java中是通过使用无锁编程来实现的,即在更新数据的时候去判断之前有没有别的线程更新了这个数据
如果这个数据没有被更新,当前线程将自己修改的数据成功写入;
如果这个数据已经被其它线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等
【判断规则】
- 版本号机制Version
- CAS(Compare And Swap,比较并交换)算法,Java原子类中的递增操作就是通过CAS自旋实现的
乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升
二、八锁案例
package com.sisyphus.Lock;import java.util.concurrent.TimeUnit;class Phone{ //资源类public static synchronized void sendEmail(){try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println("-----sendEmail");}public synchronized void sendSMS(){System.out.println("-----sendSMS");}public void hello(){System.out.println("-----hello");}
}/** 题目:谈谈你对多线程锁的理解,8锁案例说明** 口诀:线程 操作 资源类** 八锁案例说明:* 1 标准情况,有a、b两个线程,请问先打印邮件还是短信【结果:邮件】* 2 sendEmail方法中加入暂停3秒钟,请问先打印邮件还是短信【结果:邮件】* 3 添加一个普通的hello方法,替换掉线程b中的sendSMS方法,请问先打印邮件还是hello【结果:hello】* 4 有两部手机,请问先打印邮件还是短信【结果:短信】* 5 将sendEmail和sendSMS修改为静态同步方法,有1部手机,请问先打印邮件还是短信【结果:邮件】* 6 sendEmail和sendSMS为静态同步方法,有2部手机,请问先打印邮件还是短信【结果:邮件】* 7 有1个静态同步方法,有1个普通同步方法,有1部手机,请问先打印邮件还是短信【结果:短信】* 8 有1个静态同步方法,有1个普通同步方法,有2部手机,请问先打印邮件还是短信【结果:短信】*/
public class Lock8Demo {public static void main(String[] args) { //一切程序的入口Phone phone = new Phone();Phone phone2 = new Phone();new Thread(() -> {phone.sendEmail();},"a").start();//暂停200ms,确保线程a先启动try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {//phone.sendSMS();//phone.hello();phone2.sendSMS();},"b").start();}
}
1.标准情况,有a、b两个线程,请问先打印邮件还是短信【结果:邮件】
sendEmail()和sendSMS()方法都被synchronized方法修饰,当synchronized修饰方法时,锁住的是整个对象的所有synchronized方法
a线程先动用了sendEmail()方法,因此a线程先获取了锁,b线程需要等待a线程执行完sendEmail()方法释放锁之后才能获取锁,最后执行sendSMS()方法
2.sendEmail方法中加入暂停3秒钟,请问先打印邮件还是短信【结果:邮件】
一个对象里面如果有多个synchronized方法,某一个时刻内,只要有一个线程去调用其中的一个synchronized方法,那么其它线程都只能等待。换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法
因此,当a线程访问sendEmail()获取了对象锁后,即使sendEmail()方法暂停了3秒,b线程也无法获取对象锁,也无法执行sendSMS()方法,只有当a线程的sendEmail()方法执行完成释放对象锁后,b线程才可以获取对象锁
3.添加一个普通的hello方法,替换掉线程b中的sendSMS方法,请问先打印邮件还是hello【结果:hello】
hello()没有被synchronizd修饰,因此访问该方法的线程并不需要参与锁竞争,在a线程执行sendEmail()的暂停期间,b线程访问hello()方法,并打印hello
4.有两部手机,请问先打印邮件还是短信【结果:短信】
有两部手机,即有两把对象锁,a线程获取其中一把,b线程获取另外一把,它们没有任何冲突,在a线程获取其中一把对象锁后执行sendEmalil()方法的暂停期间,b线程访问另一个对象的sendSMS()方法并打印短信
5.将sendEmail和sendSMS修改为静态同步方法,有1部手机,请问先打印邮件还是短信【结果:邮件】
当synchronized修饰静态方法时,锁住的是整个类的所有静态synchronized方法
6.sendEmail和sendSMS为静态同步方法,有2部手机,请问先打印邮件还是短信【结果:邮件】
当synchronized修饰静态方法时,锁住的是整个类的所有静态synchronized方法
7.有1个静态同步方法,有1个普通同步方法,有1部手机,请问先打印邮件还是短信【结果:短信】
一个类的类锁和对象锁并不冲突,当a线程访问sendEmail()获取了类锁(Class对象)后,b线程可以访问sendSMS()并获取对象锁
8.有1个静态同步方法,有1个普通同步方法,有2部手机,请问先打印邮件还是短信【结果:短信】
略
三、synchronized
1.字节码分析
synchronized 的作用范围
- 作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁
- 作用域于代码块,对括号里配置的对象加锁
- 作用于静态方法,当前类加锁,进入同步代码前要获得当前类对象的锁
字节码文件反汇编
javap -c xxx.class 对代码进行反汇编
javap -v xxx.class 输出附加信息(包括行号、本地变量表、反汇编等信息)
package com.sisyphus.Lock;public class LockSyncDemo {Object object = new Object();public void m1(){synchronized(object){System.out.println("-----hello synchronized code block");}}public static void main(String[] args) {}
}
执行javap -c
Compiled from "LockSyncDemo.java"
public class com.sisyphus.Lock.LockSyncDemo {java.lang.Object object;public com.sisyphus.Lock.LockSyncDemo();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: new #2 // class java/lang/Object8: dup9: invokespecial #1 // Method java/lang/Object."<init>":()V12: putfield #3 // Field object:Ljava/lang/Object;15: returnpublic void m1();Code:0: aload_01: getfield #3 // Field object:Ljava/lang/Object;4: dup5: astore_16: monitorenter //获得锁7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;10: ldc #5 // String -----hello synchronized code block12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V15: aload_116: monitorexit //退出锁17: goto 2520: astore_221: aload_122: monitorexit //两次退出锁以保证异常情况也能退出锁23: aload_224: athrow25: returnException table:from to target type7 17 20 any20 23 20 anypublic static void main(java.lang.String[]);Code:0: return
}
结论:synchronized 通过管程 monitor 实现
管程是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。
这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中,把信号量及其操作原语“封装”在一个对象内部。
管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看作一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制
2.底层原语
ObjectMonitor.java→objectMonitor.cpp→objectMonitor.hpp
部分hpp代码
ObjectMonitor() {_header = NULL;_count = 0; //用来记录当前线程获取锁的次数_waiters = 0,_recursions = 0; //锁的重入次数_object = NULL;_owner = NULL; //指向持有 ObjectMonitor 对象的线程_WaitSet = NULL; //存放处于 wait 状态的线程队列_WaitSetLock = 0 ;_Responsible = NULL ;_succ = NULL ;_cxq = NULL ;FreeNext = NULL ;_EntryList = NULL ; //存放处于等待锁 block 状态的线程队列_SpinFreq = 0 ;_SpinClock = 0 ;OwnerIsThread = 0 ;_previous_owner_tid = 0;}
四、公平锁和非公平锁
package com.sisyphus.Lock;import java.util.concurrent.locks.ReentrantLock;class Ticket{private int number = 50;ReentrantLock lock = new ReentrantLock(true); //true表示公平锁,false表示非公平锁,默认非公平public void sale(){lock.lock();try{if (number > 0){number--;System.out.println(Thread.currentThread().getName() + "卖出,还剩下:" + number);}}finally {lock.unlock();}}
}public class SaleTicketDemo {public static void main(String[] args) {Ticket ticket = new Ticket();new Thread(() -> {for (int i = 0; i < 55; i++) ticket.sale();},"a").start();new Thread(() -> {for (int i = 0; i < 55; i++) ticket.sale();},"b").start();new Thread(() -> {for (int i = 0; i < 55; i++) ticket.sale();},"c").start();}
}
公平锁
多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买,后来的人在队尾排队
非公平锁
多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程先获取锁,导致某个线程饥饿
为什么默认非公平锁
- 恢复挂起的线程是需要消耗时间的,从开发人员来看这个时间微乎其微,但是从 CPU 的角度看,这个时间差还是非常明显的,所以非公平锁能更充分地利用 CPU 的时间片
- 使用多线程必须要考虑线程切换的开销,非公平锁可以减少线程切换
五、可重入锁
可重入锁又名递归锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁,不会因为外层获取的锁没释放而阻塞
synchronized 可重入原理
每个锁对象都拥有一个锁计数器和一个指向持有该锁的线程的指针。当执行 monitorenter 时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java 虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加 1。在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加 1,否则需要等待,直至持有线程释放该锁。当执行 monitorexit 时,Java 虚拟机则需要将锁对象的计数器减 1。计数器为零,代表锁已释放。
六、死锁
1.是什么
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁
2.手写死锁
package com.sisyphus.Lock;import java.sql.Time;
import java.util.concurrent.TimeUnit;public class DeadLock {//创建两个对象static Object a = new Object();static Object b = new Object();public static void main(String[] args) {new Thread(()->{synchronized (a){System.out.println(Thread.currentThread().getName() + " 持有锁 a,试图获得锁 b");try{TimeUnit.SECONDS.sleep(1);//睡眠 1 s}catch (InterruptedException e){e.printStackTrace();}synchronized (b){System.out.println(Thread.currentThread().getName() + "获得锁 b"); //死锁的情况无法获取}}},"AA").start();new Thread(()->{synchronized (b){System.out.println(Thread.currentThread().getName() + " 持有锁 b,试图获得锁 b");try{TimeUnit.SECONDS.sleep(1);//睡眠 1 s}catch (InterruptedException e){e.printStackTrace();}synchronized (a){System.out.println(Thread.currentThread().getName() + "获得锁 a"); //死锁的情况无法获取}}},"BB").start();}
}
3.死锁排查命令
原生命令
jps -l,找到 java 程序

jstack [进程号],查看死锁信息

图形化界面



相关文章:
【JUC2022】第二章 多线程锁
【JUC2022】第二章 多线程锁 文章目录【JUC2022】第二章 多线程锁一、乐观锁与悲观锁1.悲观锁2.乐观锁二、八锁案例1.标准情况,有a、b两个线程,请问先打印邮件还是短信【结果:邮件】2.sendEmail方法中加入暂停3秒钟,请问先打印邮件…...
快学会这个技能-.NET API拦截技法
大家好,我是沙漠尽头的狼。 本文先抛出以下问题,请在文中寻找答案,可在评论区回答: 什么是API拦截?一个方法被很多地方调用,怎么在不修改这个方法源码情况下,记录这个方法调用的前后时间&…...
stm32f407探索者开发板(十八)——串口通信实验讲解(USART_RX_STA流程图详解)
文章目录一、uart_init(串口初始化)二、USART1_IRQHandler(串口1中断服务程序)三、main.c(主函数)四、关于printf的支持一、uart_init(串口初始化) 就是根据上一篇的一样的步骤&…...
Hystrix资源隔离
目录资源隔离使用资源隔离的好处基于Hystrix实现微服务中资源隔离基于Hystrix线程池隔离实现资源隔离利用 HystrixCommand 获取单条数据利用 HystrixObservableCommand 批量获取数据基于 Hystrix 信号量机制实现资源隔离资源隔离 资源隔离是什么? 资源隔离是指把对…...
字符串(一)-- LeetCode[3] 无重复字符的最长子串
1 无重复字符的最长子串 1.1 题目描述 给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。 示例 2: 输入: s “bbbbb” 输出: 1 解释…...
Qt中修改界面类的类名时需要注意的几个修改点
有些时候因为一些原因,需要修改Qt中创建的界面类,需要特别注意几个修改点。 比如将test类修改为test2类 修改test.h名称为test2.h文件;修改test.cpp名称为test2.cpp文件;修改test.ui名称为test2.ui文件;修改pro文件中…...
【Spring6】| Spring启示录、Spring概述
目录 一:Spring启示录 1. OCP开闭原则 2. 依赖倒置原则DIP 3. 控制反转IoC 二:Spring概述 1. Spring简介 2. Spring8大模块 3. Spring特点 一:Spring启示录 引言:前面我们已经学习了三层架构:表示层、业务层、…...
react源码中的fiber架构
先看一下FiberNode在源码中的样子 FiberNode // packages/react-reconciler/src/ReactFiber.old.js function FiberNode(tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) {// Instancethis.tag tag;this.key key;this.elementType null;t…...
C++类和对象-继承多态
继承 继承是面向对象三大特性之一 定义类时,下级别的成员除了拥有上一级的共性,还有自己的特性,就可以考虑使用继承的技术,减少代码的重复 继承的基本语法 语法:class 子类 : 继承方式 父类 子类也被成为派生类父类…...
appium自动化测试
获取应用包名和入口activity:aapt命令 aapt目录: 安卓sdk的build-tools目录下(如果要在cmd里直接运行,要配置环境变量,否则需要在aapt所在目录下打开cmd) 示例: adt-bundle-windows-x86_64-20140702\sdk\build-too…...
打印流、转换流、数据流 、随机访问流
Java知识点总结:想看的可以从这里进入 目录5、打印流6、转换流7、数据流8、随机访问流5、打印流 实现将基本数据类型的数据格式转化为字符串输出,它们提供了一系列重载的print()和println()方法,用于多种数据类型的输出,这种流不会…...
Java的4种访问权限?
1、public: 所修饰的类、变量、方法,在内外包均具有访问权限;2、protected: 这种权限是为继承而设计的,protected所修饰的成员,对所有子类是可访问的,但只对同包的类是可访问的,对外…...
APP任务模块功能借助php-resque实现业务解耦
先上设计图 说明:任务模块分一次性任务和每日任务,可能还包括男女用户任务区分 处理步骤: 一、同步任务数据库 1.1、任务列表数据库 1.2、完成任务数据库 二、搭建即时消息队列 一、composer require resque/php-resque二、因为服务器red…...
怎么做,才能在职场中晋升?
1 主动原则:主动做事 工作要积极主动,刚进入职场的同学,以为“服从命令听指挥”“领导指哪打哪”就是积极主动,结果易养 1.1 不好习惯 ① 认为主管肯定会帮你搞定晋升 你可能非常信任主管,认为自己只要把主管安排的…...
Vulnhub靶场----2、DC-2
文章目录一、环境搭建二、渗透流程三、思路总结一、环境搭建 DC-2下载地址:https://download.vulnhub.com/dc/DC-2.zip kali:192.168.144.148 DC-2:192.168.144.150 添加hosts文件:192.168.144.150 DC-2 二、渗透流程 nmap -A -…...
Java 基础(3)—synchornized 关键字简单理解
一、synchronized 修饰同步代码块 用 synchronized 修饰代码段作为同步锁,代码如下: public class LockDemo {public Object object new Object();public void show(){synchronized (object) {System.out.println(">>>>>>hell…...
【Linux】调试工具gdb的使用
环境:centos7.6,腾讯云服务器Linux文章都放在了专栏:【Linux】欢迎支持订阅🌹前言在前文,我们已经讲解了vim工具以及gcc/g的使用,我们可以进行编写代码以及编译代码了,但是还没有学习如何在Linu…...
大数据知识图谱项目——基于知识图谱的医疗知识问答系统(详细讲解及源码)
基于知识图谱的医疗知识问答系统 一、项目概述 本项目基于医疗方面知识的问答,通过搭建一个医疗领域知识图谱,并以该知识图谱完成自动问答与分析服务。本项目以neo4j作为存储,基于传统规则的方式完成了知识问答,并最终以关键词执…...
威马汽车:跃马扬鞭未竟,鞍马劳顿难行?
“活下去,像牲口一样地活下去。” 威马汽车创始人、董事长兼CEO沈晖1月在社交媒体上分享的电影台词,已然成为威马近况的真实写照。 来源:新浪微博威马汽车沈晖Freeman 最近,网上出现了大量关于“威马汽车将实施全员停薪留职”的…...
【网络】网络基础
🥁作者: 华丞臧. 📕专栏:【网络】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉 LeetCode刷题网站 文章…...
黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 (TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
