Java synchronized实现原理深度剖析

Sync的实现原理

sync是jvm的内置锁,底层是通过对象监视器来实现。而对象监视器的底层实现是通过cas+自旋或者操作系统的互斥量来实现的。通过javap -c 命令可以查看到sync方法前后有成对的monitorenter/monitorexit指令。

cas: compare and swap ,比对并交换。
这个操作是cpu指令级的功能,可以保证原子性。
这个操作需要三个参数:1.数据存放的地址Addr  2.原来的值A  3.期望改变后的值B。
只有当Addr数据为A的时候才会成功把值更改为B

在jvm早期中,sync是通过操作系统的互斥量来实现的,这就会涉及到操作系统的用户态和内核态的切换,这是非常消耗系统资源的操作。所以后来引入了轻量级锁,即通过cas+自旋的方式来实现。

那么执行同步代码块的线程是如何进行同步的呢?它们总有一个共同的存储锁信息的地方吧? 是的,那就是对象的mark word

对象的mark word在什么地方呢?
在对象的对象头里。这里扩展下对象在jvm中的内存结构:
对象结构

我们重点关注下对象头中的mark word,那么mark word的结构又是怎样的呢?32位jvm中如下:
mark word结构

线程就是通过对象的mark word来进行同步的。

在上图可以看到mark word中记录了对象锁的状态,分为无锁、偏向锁、轻量级锁、重量级锁。这几种状态的转变规则为无锁->偏向锁->轻量级锁->重量级锁,这个规则称之为锁的升级。注意:锁只能升级不能降级。但偏向锁可以降级为无锁,其他的不行。

锁的升级过程

见图:

sync锁的升级过程

这张图用文字描述清楚感觉太费劲,文字还多,说不定吃力不讨好,所以这个图要慢慢品,细细品,多次品。品的时候建议假设有多个线程在竞争锁。

品的时候有个难点可能是偏向锁的撤销那,偏向锁的撤销是在有锁竞争的情况下才会撤销。为什么呢 ?我们优先要明白偏向锁的设计目的是什么? 因为可能存在这种情况:这个同步代码块只有一个线程在访问。如果没有偏向锁,那么根据上图这个线程会多次进行cas操作,这个显然是没有必要的,于是为了优化就设计了偏向锁。使得在只有一个线程访问同步代码块的时候,只需要比对之前偏向的线程是不是自己,如果是,则获得锁。

扩展

矛盾1

A: 重量级锁中的阻塞(挂起线程/恢复线程): 需要转入内核态中完成,有很大的性能影响。

B: 锁大多数情况都是在很短的时间执行完成。

解决方案: 引入轻量锁(通过自旋来完成锁竞争)。

矛盾2

A: 轻量级锁中的自旋: 占用CPU时间,增加CPU的消耗(因此在多核处理器上优势更明显)。

B: 如果某锁始终是被长期占用,导致自旋如果没有把握好,白白浪费CPU资源。

解决方案: JDK5中引入默认自旋次数为10(用户可以通过-XX:PreBlockSpin进行修改), JDK6中更是引入了自适应自旋(简单来说如果自旋成功概率高,就会允许等待更长的时间(如100次自旋),如果失败率很高,那很有可能就不做自旋,直接升级为重量级锁,实际场景中,HotSpot认为最佳时间应该是一个线程上下文切换的时间,而是否自旋以及自旋次数更是与对CPUs的负载、CPUs是否处于节电模式等息息相关的)。

矛盾3

A: 项目中代码块中可能绝大情况下都是多线程访问。

B: 每次都是先偏向锁然后过渡到轻量锁,而偏向锁能用到的又很少。

解决方案: 可以使用-XX:-UseBiasedLocking=false禁用偏向锁。

矛盾4

A: 代码中JDK原生或其他的工具方法中带有大量的加锁。

B: 实际过程中,很有可能很多加锁是无效的(如局部变量作为锁,由于每次都是新对象新锁,所以没有意义)。

解决方法: 引入锁削除(虚拟机即时编译器(JIT)运行时,依据逃逸分析的数据检测到不可能存在竞争的锁,就自动将该锁消除)。

矛盾5

A: 为了让锁颗粒度更小,或者原生方法中带有锁,很有可能在一个频繁执行(如循环)中对同一对象加锁。

B: 由于在频繁的执行中,反复的加锁和解锁,这种频繁的锁竞争带来很大的性能损耗。

解决方法: 引入锁扩大/锁膨胀(会自动将锁的范围拓展到操作序列(如循环)外, 可以理解为将一些反复的锁合为一个锁放在它们外部)。

扩展

本人自己建了一个jdk8和jdk7的源码阅读仓库,会在阅读源码的过程中添加一些注释。
感兴趣的朋友可以一起来添加对代码的理解。仓库地址:
jdk8:
	github: https://github.com/rocky-peng/jdk8-sourcecode-read
	gitlab: https://gitlab.com/rocky_peng/jdk8
jdk7:
	github: https://github.com/rocky-peng/jdk7-sourcecode-read
	gitlab: https://gitlab.com/rocky_peng/jdk7
github和gitlab是完全自动同步的。   
jdk8和jdk7的源码来源于jdk8和jdk7的src.zip文件。  
发布了84 篇原创文章 · 获赞 66 · 访问量 24万+

猜你喜欢

转载自blog.csdn.net/u012358984/article/details/102961874