第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语言规范第三版中 ...
随机推荐
- 51单片机定时器实现LED闪烁
要启用一个定时器,先要开启定时器,然后产生中断 系统中断: 初始化程序应完成如下工作: 对TMOD赋值,以确定T0和T1的工作方式. 计算初值,并将其写入TH0.TL0或TH1.TL1. 中断方式时, ...
- Fundebug能够捕获这些BUG
摘要:Fundebug的JavaScript监控插件更新至0.1.0,可以监控3种不同类型的前端BUG:JavaScript执行错误.资源加载错误.HTTP请求错误. 从简单的onerror开始,Fu ...
- CSS3布局之多列布局columns详解
columns语法:columns:[ column-width ] || [ column-count ]设置或检索对象的列数和每列的宽度 其中:[ column-width ]:设置或检索对象每列 ...
- 洛谷P4592 [TJOI2018]异或(可持久化01Trie)
题意 题目链接 可持久化01Trie板子题 对于两个操作分别开就行了 #include<bits/stdc++.h> using namespace std; const int MAXN ...
- Android为TV端助力 StringBuffer 和StringBuilder
如果我们的程序是在单线程下运行,或者是不必考虑到线程同步问题,我们应该优先使用StringBuilder类:如果要保证线程安全,自然是StringBuffer. 除了对多线程的支持不一样外,这两个类的 ...
- 在Win32程序中嵌入Edge浏览器组件
代码未经测试,只做个记录 据说只是改了UA. 在注册表修改这个路径,并且把自己程序添加进去,写一个浏览器控件的版本号,只要高于12000就自动改为Edge. For 64bit application ...
- Android getprop 读取的属性哪里来的?
Android getprop 和 setprop 可以对系统属性进行读取和设置. 通过串口执行以下 geyprop 打印出来的属性让你一目了然. 属性出来了,但是在哪里设置的呢,这里有两个 ...
- 前端 CSS预处理器Less
引文 Less是一种动态的样式语言.Less扩展了CSS的动态行为,比如说,设置变量(Variables).混合书写模式(Mixins).操作(Operations)和功能(Functions)等等, ...
- 编写寄宿于windows服务的WCF服务
由于业务中有些任务需要在后台静默长期运行,或者有些服务队响应的要求比较苛刻,这样的WCF服务就不适合寄宿于IIS中.IIS每隔一段时间w3wp进程会闲置超时,造成服务的运行停止,因此这种耗时或者定时任 ...
- sublime text 3 优化配置
目录 1. sublime text 3 模板插件 SublimeTmpl 配置 修改模板内容格式 修改快捷键 2. 设置sublime text的 TAB 为4个空格 3. 添加markdown支持 ...