Java 并发编程_详解 synchronized 和 volatile
文章目录
- 1. synchronized 的应用
- 1.1 基础知识
- 1.2 synchronized 语法
- 2. Monitor概念
- 3. Synchronized原理进阶
- 3.1 对象头格式
- 3.2 轻量级锁(用于优化Monitor这类的重量级锁)
- 3.3 锁膨胀
- 3.4 自旋优化
- 3.4 偏向锁(用于优化轻量级锁重入)
- 3.4.1 撤销偏向
- 3.4.2 批量重偏向
- 3.4.3 批量撤销
- 3.4.4 锁消除
- 4. volatile
- 4.1 CPU 术语
- 4.2 实现可见性
1. synchronized 的应用
1.1 基础知识
临界区 Critical Section
- 一个程序运行多个线程本身是没有问题的
- 问题出在多个线程访问共享资源
- 多个线程读共享资源其实也没有问题
- 在多个线程对共享资源读写操作时发生指令交错,就会出现问题
- 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
例如,下面代码中的临界区
static int counter = 0;static void increment()
// 临界区
{ counter++;
}static void decrement()
// 临界区
{ counter--;
}
竞态条件 Race Condition
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
1.2 synchronized 语法
synchronized,即俗称的【对象锁】,它采用互斥的方式让同一 时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住(blocked)。这样就能保证拥有锁 的线程可以安全的执行临界区内的代码,不用担心线程上下文切换
(1) 锁对象
synchronized(对象) {//临界区
}
(2)synchronized加在方法上
- 加在成员方法上
public class Demo {//在方法上加上synchronized关键字public synchronized void test() {}//等价于public void test() {synchronized(this) {}}
}
- 加在静态方法上
public class Demo {//在静态方法上加上synchronized关键字public synchronized static void test() {}//等价于public void test() {synchronized(Demo.class) {}}
}
2. Monitor概念
Monitor 被翻译为监视器或者管程
每个 Java对象都可以关联一个Monitor 对象,如果使用synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向Monitor 对象的指针
- 当线程执行到临界区代码时,如果使用了synchronized,会先查询synchronized中所指定的对象(obj)是否绑定了Monitor。
- 如果没有绑定,则会先去去与Monitor绑定,并且将Owner设为当前线程。
- 如果已经绑定,则会去查询该Monitor是否已经有了Owner
- 如果没有,则Owner与将当前线程绑定
- 如果有,则放入EntryList,进入阻塞状态(blocked)
- 当Monitor的Owner将临界区中代码执行完毕后,Owner便会被清空,此时EntryList中处于阻塞状态的线程会被叫醒并竞争,此时的竞争是非公平的
- 注意:
- 对象在使用了synchronized后与Monitor绑定时,会将对象头中的Mark Word置为Monitor指针。
- 每个对象都会绑定一个唯一的Monitor,如果synchronized中所指定的对象(obj)不同,则会绑定不同的Monitor
- 如果对于对象结构不熟悉可以看 这篇博客
举例:
public class MonitorTest {static final Object lock = new Object();static int counter = 0;public static void main(String[] args) {synchronized (lock) {counter++;}}
}
3. Synchronized原理进阶
3.1 对象头格式
3.2 轻量级锁(用于优化Monitor这类的重量级锁)
轻量级锁使用场景:当一个对象被多个线程所访问,但访问的时间是错开的(不存在竞争),此时就可以使用轻量级锁来优化。
创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录对象,内部可以存储锁定对象的mark word对象头(不再一开始就使用Monitor)
让锁记录中的Object reference指向锁对象(Object),并尝试用cas去替换Object中的mark word,将此mark word放入lock record中保存
如果cas替换成功,则将Object的对象头替换为锁记录的地址和状态 00(轻量级锁状态),并由该线程给对象加锁
如果cas失败,有两种情况
- 如果是其他线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程
- 如果是自己执行了synchronized锁重入,那么再添加一条Lock Record 作为重入的计数
- 重入:同一个线程给同一个对象加锁
- 重入:同一个线程给同一个对象加锁
当退出synchronized 代码块(解锁时)如果有取值为null的锁记录,表示有重入,这时重置锁记录,表示重入技术减一
当退出synchronized代码块(解锁时)锁记录的值不为null,这时使用cas将Mark Word的值恢复给对象头
- 成功则解锁成功
- 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程
3.3 锁膨胀
如果一个线程在给一个对象加轻量级锁时,cas替换操作失败(因为此时其他线程已经给对象加了轻量级锁),此时该线程就会进入锁膨胀过程
- 此时便会给对象加上重量级锁(使用Monitor)
- 将对象头的Mark Word改为Monitor的地址,并且状态改为01(重量级锁)
- 并且该线程放入入EntryList中,并进入阻塞状态(blocked)
3.4 自旋优化
重量级锁竞争时,还可以使用自旋来优化,如果当前线程在自旋成功(使用锁的线程退出了同步块,释放了锁),这时就可以避免线程进入阻塞状态。
- 第一种情况 成功
- 第二种情况 失败
3.4 偏向锁(用于优化轻量级锁重入)
轻量级锁在没有竞争时,每次重入(该线程执行的方法中再次锁住该对象)操作仍需要cas替换操作,这样是会使性能降低的。
所以引入了偏向锁对性能进行优化:在第一次cas时会将线程的ID写入对象的Mark Word中。此后发现这个线程ID就是自己的,就表示没有竞争,就不需要再次cas,以后只要不发生竞争,这个对象就归该线程所有。
对象头的状态
- Normal:一般状态,没有加任何锁,前面62位保存的是对象的信息,最后2位为状态(01),倒数第三位表示是否使用偏向锁(未使用:0)
- Biased:偏向状态,使用偏向锁,前面54位保存的当前线程的ID,最后2位为状态(01),倒数第三位表示是否使用偏向锁(使用:1)
- Lightweight:使用轻量级锁,前62位保存的是锁记录的指针,最后两位为状态(00)
- Heavyweight:使用重量级锁,前62位保存的是Monitor的地址指针,后两位为状态(10)
- 如果开启了偏向锁(默认开启),在创建对象时,对象的Mark Word后三位应该是101
- 但是偏向锁默认是有延迟的,不会再程序一启动就生效,而是会在程序运行一段时间(几秒之后),才会对创建的对象设置为偏向状态
- 如果没有开启偏向锁,对象的Mark Word后三位应该是001
3.4.1 撤销偏向
以下几种情况会使对象的偏向锁失效
- 调用对象的hashCode方法
- 多个线程使用该对象(无竞争状态会升级为轻量级锁)
- 调用了wait/notify方法(调用wait方法会导致锁膨胀而使用重量级锁)
3.4.2 批量重偏向
- 如果对象虽然被多个线程访问,但是线程间不存在竞争,这时偏向T1的对象仍有机会重新偏向T2
- 重偏向会重置Thread ID
- 当撤销超过20次后(超过阈值),JVM会觉得是不是偏向错了,这时会在给对象加锁时,重新偏向至加锁线程。
3.4.3 批量撤销
当撤销偏向锁的阈值超过40以后,就会将整个类的对象都改为不可偏向的,新建的对象也是不可偏向的
3.4.4 锁消除
举例:
public void test() {Object o = new Object();synchronized (o) {//doSomeThing}}
在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。如果没有,那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步。这样就能大大提高并发性和性能。这个取消同步的过程就叫同步省略,也叫锁消除
4. volatile
在多线程并发编程中 synchronized 和 volatile 都扮演着重要的角色,volatile 是轻量级的 synchronized ,它在多处理器开发中保证了共享变量的“可见性”,可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。如果volatile 变量修饰符使用恰当的话,他比synchronized的使用和执行成本更低
4.1 CPU 术语
- 内存屏障:是一组处理器指令,用于实现对内存操作的顺序限制
- 缓冲行:缓存中可以分配的最小存储单位。处理器填写缓存线时会加载整个缓存线,需要使用多个主内存读周期
- 原子操作:不可终端的一个或一系列操作
- 缓存行填充:当处理器识别到从内存中读取操作数是可缓存的,处理器读取整个缓存行到适当的缓存
- 缓存命中:如果进行高速缓存行填充操作的内存位置仍然是下次处理器访问的地址时,处理器从缓存行中读取操作数,而不是从内存中读取
- 写命中:当处理器将操作数写回到一个内存缓存的区域时,它首先会检查这个缓存的内存地址是否在缓存行中,如果存在一个有效的缓存行,则处理器将这个操作数写回到缓存,而不是写回内存,这个操作被称为写命中
- 写缺失:一个有效的缓存行被写入到不存在的内存区域
4.2 实现可见性
java 代码 :
instance = new Singleton(); // instance 是 volatile 变量
转变成汇编代码:
0x01a3de: move $0 x 0, 0 x 1104800(%esi);
0x01a3de24: lock add1 $0 x 0,(%esp)
有volatile修饰的共享变量进行写操作的时候会多出第二行代码,主要在于Lock前缀
Lock 前缀的指令在多核处理器会引发两件事
- Lock 前缀指令会引起处理器缓存回写到内存
- 一个处理器的缓存回写到内存会导致其他处理器的缓存无效
Java 并发编程_详解 synchronized 和 volatile相关推荐
- python java混合编程_详解java调用python的几种用法(看这篇就够了)
java调用python的几种用法如下: 在java类中直接执行python语句 在java类中直接调用本地python脚本 使用Runtime.getRuntime()执行python脚本文件(推荐 ...
- Java并发编程AQS详解
本文内容及图片代码参考视频:https://www.bilibili.com/video/BV12K411G7Fg/?spm_id_from=333.788.recommend_more_video. ...
- Java并发编程——ConcurrentHashMap详解
引出 场景:针对用户来做一个访问次数的记录. 通过HashMap进行记录,key为用户名,value为访问次数. public class ConcurrentHashMapDemo {private ...
- Java并发编程——ForkJoin详解
概念 Fork/Join 框架是 Java7 提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架.类似于Java 8中的paralle ...
- 学习笔记:Java 并发编程①_基础知识入门
若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 视频下载: ...
- 学习笔记:Java 并发编程⑥_并发工具_JUC
若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 配套资料: ...
- 学习笔记:Java 并发编程②_管程
若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 配套资料: ...
- 学习笔记:Java 并发编程④_无锁
若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 配套资料: ...
- 它来了,阿里架构师的“Java多线程+并发编程”知识点详解手册,限时分享
自学Java的时候,多线程和并发这一块可以说是最难掌握的部分了,很多小伙伴表示需要一些易于学习和上手的资料. 所以今天这份「Java并发学习手册」就是一份集中学习多线程和并发的手册,PDF版,由Red ...
最新文章
- 为什么c语言编译器闪屏,C语言贪吃蛇闪屏问题,求大神!!!
- android开发关于和使用本机内存,内置存储卡和外置存储卡大揭秘
- 2017年第八届蓝桥杯C/C++ A组国赛 —— 第一题:平方十位数
- 设△ABC的内角A,B,C,所对的边分别为a,b,c,且acosB-bcosA=3/5c,则tan(A-B)的最大值为
- vscode 离线安装python插件_vscode for Python插件下载-Visual Studio Code Python插件下载0.9.1 官方版-西西软件下载...
- I00020 计算卡特兰数函数
- BATJ等大厂最全经典面试题分享
- MySQL优化详解(二)——数据库架构和使用优化
- angular学习之路(一)
- QLab Pro如何对工作区进行设置
- 手机股票软件哪个好?这几款炒股app你不能错过!
- Comsol学习——经典案例:散热器的冷却性能
- 数据库备份的方式有哪些
- BCM业务连续性管理
- 解决Ubuntu Pycharm图标问号的方法
- adb 查看屏幕大小_如何从adb命令行获取Android设备的屏幕尺寸?
- 比赛介绍评委的pp咋做_播音主持专业如何做自我介绍?
- jBox----弹出层插件
- 云计算--day07
- 由点及面,一叶知秋------集合大家庭
热门文章
- C语言实验报告册-20163a,C语言实验报告册20163a-资源下载人人文库网
- 用计算机思维解决问题的例子,简单易懂的思维模型:解决问题篇
- 红警2你值得拥有(游戏人生)
- 使用React Hooks 时要避免的5个错误!
- 6 怎么选公司?面试3大招,离职都有哪些事宜要注意--绝密,程序员大厂面试求职大揭秘!
- 2022年衡量技术债务的8个主要指标
- 个人技术博客的选择:CSDN、博客园、简书、知乎专栏还是Github Page?
- 小强IT游记之大连行
- 数据分析终极一问:自然增长率,到底怎么算才合理!
- 七万字,151张图,通宵整理消息队列核心知识点总结!这次彻底掌握MQ!