第2章 Java并发机制的底层实现原理
2.2 synchronized的实现原理与应用
当一个线程A执行字节码时遇到monitorenter指令时,会首先检查该指令关联的Object的对象头中的Mark Word状态。
2.2.1 如果是偏向锁
如果2bit标志位为01代表此时处于偏向锁状态。
如果2bit标志位为01且1bit的标志位为1,代表该对象此时处于偏向锁且锁被获取状态。
- 如果Mark Word的线程ID是当前线程的ID,代表该线程之前已经获得了该偏向锁,那么可以继续执行同步代码块
- 如果当前Mark Word存的线程的ID不是当前线程ID,结合之前的条件可以推测出有别的线程获得了偏向锁。那么根据Mark Word里存的获得了偏向锁线程的ID记为B线程,如果B线程此时不处于活动状态,那么重新设置Mark Word,把此时的Mark Word设置为无线程获得偏向锁的偏向锁状态,然后A线程去执行CAS操作修改Mark Word的ThreadID,让该对象的偏向锁指向A线程;如果B线程还处于活动状态,那么去检查B线程是否需要继续持有偏向锁,即B线程是否还执行临界区,如果不是,也要“重新设置Mark Word,把此时的Mark Word设置为无线程获得偏向锁的偏向锁状态,然后A线程去执行CAS操作修改Mark Word的ThreadID,让该对象的偏向锁指向A线程”,如果很不幸的B线程处在活跃的状态并且也要继续执行临界区,此时的情况是AB两个线程同时需要进入临界区,偏向锁已经无能为力,偏向锁升级我轻量锁
如果2bit标志位为01且1bit的标志位为0,代表该对象处于偏向锁未被获取的状态,
那么执行CAS操作修改ThreadID。如果此时失败了呢?书上没说,我猜测是如果CAS操作失败那么一定是还有另一个线程此时也在CAS操作修改ThreadID。此时应该也要膨胀到轻量锁。
2.2.2 如果是轻量级锁
这里不同的资料给出了不同的解释,即关于什么时候一个线程会请求一个轻量级的锁。https://www.jianshu.com/p/c5058b6fe8e5我比较认同这里的看法,当mark word的标志位是001时,即当前无锁状态且不允许使用偏向锁的时候,就会请求使用轻量级锁。这一点和那副著名的图有所区别。
当试图获得一个轻量级锁的时候,1、首先在当前线程的栈帧中分配一个Lock Record记录,并把MarkWord复制到当前的LockRecord中 2、尝试使用CAS操作去把对象头的mark word改成指向当前LockRecord的指针

inline intptr_t Atomic::cmpxchg_ptr(intptr_t exchange_value, volatile intptr_t* dest, intptr_t compare_value) {
return (intptr_t)cmpxchg((jint)exchange_value, (volatile jint*)dest, (jint)compare_value);
}
上面这段c++就是获取偏向锁的过程,重点关注红色框里的CAS操作。下面就CAS方法的签名,可以看到第一个参数是要改变的值,最后一个参数是用来比较的值。所以综上可以看到获取轻量级锁的本质就是拿自己LockRecord里的markeord和此时此刻对象头的markWord比较并执行cas操作,成功了,当前线程就获得了轻量级锁。
那如果失败了呢?我虽然不太能看懂这段c++,但是我能看懂if逻辑。如果失败了的话,会把当前线程栈帧的lockRecord里的markword替换成一个名为unused_mark的东西(这东西是啥?),然后剩下的就是进入了inflate膨胀成重量级锁的阶段了。这和书上不一样啊,不是自旋吗?????
书还能错了???

上面是轻量级锁的释放逻辑,取出LockRecord里的markword记录,用cas操作把对象头里markword换回去,成功了就释放了轻量级锁,如果失败了就inflate成重量级锁。
仔细看一下这个cas操作,第一个参数是dhw,dhw就是lockrecord里存储的加锁的时候锁对象的mark,也就是说dhw是我想写回的数据。cas里第三个参数是mark,往上看发现mark是object->mark取出来的,也就是我先从锁对象里取出了mark的值,然后把取出的mark的值作为cas操作compare的值。,所以理论上这个cas操作一定是会成功的,如果失败了,说明在从取出mark到使用mark执行cas操作之间对象的mark被别人替换了。什么情况下会被替换呢?再取出mark的时候,mark已经在轻量级锁获取的时候被替换成了指向lockrecord的指针,所以markWord不会因为别的线程尝试获取轻量级锁被替换成指向别的线程lockrecord的指针。所以我猜测,maekword被替换是由于别的线程视图获取轻量级锁,发生了锁竞争,进一步锁膨胀成重量级索,在膨胀的时候markword被更改,不是指向lockrecord的指针。
结合轻量级锁的获取和竞争可以看到,轻量级锁的膨胀有可能是两个地方发生,假设A线程获得了轻量级锁,然后B线程也想获得锁,那么在B线程试图获取轻量级锁的时候,会走到inflate这段逻辑里,所以此时锁膨胀成了重量级锁。然后在A线程释放锁的时候,发现释放锁的CAS操作失败,因为markword不是指向自己lockrecord的指针了,此时A线程也会走inflate的锁膨胀逻辑。
2.2.2 最不想看到的重量级锁
第2章 Java并发机制的底层实现原理的更多相关文章
- 【java并发编程艺术学习】(三)第二章 java并发机制的底层实现原理 学习记录(一) volatile
章节介绍 这一章节主要学习java并发机制的底层实现原理.主要学习volatile.synchronized和原子操作的实现原理.Java中的大部分容器和框架都依赖于此. Java代码 ==经过编译= ...
- (第二章)Java并发机制的底层实现原理
一.概述 Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令. ...
- 【java并发编程艺术学习】(五)第二章 java并发机制的底层实现原理 学习记录(三) 原子操作的实现原理学习
章节介绍 主要包括 术语定义.处理器如何实现原子操作.Java如何实现原子操作: 原子(atomic)本意是 不能再进一步分割的最小粒子,“原子操作” 意为 不可被中断的一个或一系列操作. 术语定义 ...
- 【java并发编程艺术学习】(四)第二章 java并发机制的底层实现原理 学习记录(二) synchronized
章节介绍 本章节主要学习 Java SE 1.6 中为了减少获得锁 和 释放锁 时带来的性能消耗 而引入的偏向锁 和 轻量级锁,以及锁的存储结构 和 升级过程. synchronized实现同步的基础 ...
- Java并发机制的底层实现原理之volatile应用,初学者误看!
volatile的介绍: Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现 ...
- 《Java并发编程的艺术》Java并发机制的底层实现原理(二)
Java并发机制的底层实现原理 1.volatile volatile相当于轻量级的synchronized,在并发编程中保证数据的可见性,使用 valotile 修饰的变量,其内存模型会增加一个 L ...
- Java 并发系列之二:java 并发机制的底层实现原理
1. 处理器实现原子操作 2. volatile /** 补充: 主要作用:内存可见性,是变量在多个线程中可见,修饰变量,解决一写多读的问题. 轻量级的synchronized,不会造成阻塞.性能比s ...
- 《Java并发编程的艺术》读书笔记:二、Java并发机制的底层实现原理
二.Java并发机制底层实现原理 这里是我的<Java并发编程的艺术>读书笔记的第二篇,对前文有兴趣的朋友可以去这里看第一篇:一.并发编程的目的与挑战 有兴趣讨论的朋友可以给我留言! 1. ...
- Java并发机制和底层实现原理
Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码转化为汇编指令在CPU上执行.Java中的并发机制依赖于JVM的实现和CPU的指令. Java语言规范第三版中 ...
随机推荐
- Linux的安装以及部署一
下载网址: Vmware: http://www.wsf1234.com/vmware- workstation-pro-v14-0-0/ Centos: http://mirrors.163.com ...
- react-conponent-secondesElapsed
<!DOCTYPE html> <html> <head> <script src="../../build/react.js">& ...
- js 金额补全处理
function returnFloat(value) { var value = Math.round(parseFloat(value) * 100) / 100; var xsd = value ...
- js 提取某()特殊字符串长度
// 提取特殊字符串长度(scrstr 源字符串 armstr 特殊字符) getStrCount: function(scrstr, armstr) { var count = 0; while ( ...
- 洛谷P4589 [TJOI2018]智力竞赛(二分答案 二分图匹配)
题意 题目链接 给出一个带权有向图,选出n + 1n+1条链,问能否全部点覆盖,如果不能,问不能覆盖的点权最小值最大是多少 Sol TJOI怎么净出板子题 二分答案之后直接二分图匹配check一下. ...
- Future FutrueTask Callable类源码说明以及原理使用
1.Future Callable FutureTask 源码说明 JDK内置的Future主要使用到了Callable接口和FutureTask类. Callable是类似于Runnable的接口, ...
- loadrunner 脚本录制-Action分类
脚本录制-Action分类 by:授客 QQ:1033553122 Action分类 l . Vuser_init 2. Vuser_end 3. Action 在lr中用户的初始化操作应该存放在V ...
- (网页)html5 canvas清空画布方法(转)
总结以下三种清空canvas画布的方式: 1. 最简单的方法:由于canvas每当高度或宽度被重设时,画布内容就会被清空,因此可以用以下方法清空: function clearCanvas() { v ...
- 修改minifest使桌面软件支持高dpi
在VisualStudio中可以很方便的设置manifest以支持高dpi的用户界面.当然也可以手工修改manifest文件来添加对高dpi的支持. QQ在高dpi方面做的尤其差,对高dpi的支持迟迟 ...
- [Python][小知识][NO.2] Python 字符串跨行连接,或拆分为多行显示
1.前言 又是一个字符串很长,但又是一种格式的小字符串直接连接而成的大字符串. 这么我们拆成多行,即美感,又易于我们修改. 例如 文件选择框中的 通配符: wildcard = "Pytho ...