第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语言规范第三版中 ...
随机推荐
- BootStrapTable 错误
异常:Cannot read property 'field' of undefined 场景:使用BootStrapTable展示数据时,控制台报错 解决:给table加上 thead 和 tbod ...
- JavaAndroid项目结构
使用Java的Android项目的结构 src: 源码文件夹 gen: 自动生成的源码文件夹 assets: 资产目录 bin: 编译生成的文件目录(apk等) libs: jar包 res: 资源文 ...
- laravel5.1 ajax post传值_token
laravel框架中只要是涉及到post传值都需要传 _token ,这是框架中为了防止crsf攻击所做的安全措施,那么我们用到ajax中的post 方式传值时,也需要在所传数据中添加一个_token ...
- laravel5.5通过Migrations修改表 的artisan命令
1,不同表的修改都需要通过命令创建一个文件 2,首先通过artisan创建对应表的一个文件 php artisan make:module:migration abtinvitcard(模块名) al ...
- 3款网页jQuery抽奖实例演示
实例演示 实例演示 实例演示
- JAX-WS Web Service小试牛刀
1.使用Eclipse新建Java工程JavaDemo 2.新建包com.kira.ws 3.在包com.kira.ws新建类Hello,代码如下 package com.kira.ws; impor ...
- Spring声明式事务配置
1.首先在/WEB-INF/applicationContext.xml添加以下内容: <!-- 配置事务管理器 --> <bean id="transactionMana ...
- 解决ui-router路由监听$stateChangeStart、$stateChangeSuccess、$stateChangeError不执行的问题
问题解答 angular1项目导入ui-router之后,使用路由监听,代码如下 angular.module('app', ['ui.router', 'ui.router.state.events ...
- ionic APP二维码插件 地址
二维码插件地址第一个 https://github.com/kyocn/BarcodeScanner git网址 下载网址 https://github.com/kyocn/BarcodeSc ...
- mac上Docker安装&初体验
Docker是什么? Docker是一个虚拟环境容器,可以将你的开发环境.代码.配置文件等一并打包到这个容器中,并发布和应用到任意平台中. 官方文档:https://docs.docker.com H ...