
他们好,我是王自强。高度关注王自强,一同聊控制技术,聊格斗游戏,从白领日常生活谈及历有。
鸽了很久是给他们找出了出格的理据–羊了。虽说流鼻血那两天吗很郁闷,显然不该下床,顺利完成日常日常生活组织工作都早已耗尽了全数的气力,显然没经心力写诗。
嘿嘿,那时他们竭尽全力自学synchronized的升级换代操作过程,现阶段只剩最终一步棋了:轻量锁->新锐锁。
透过那时的文本,期望能协助他们答疑synchronized都问啥?中除锁粗化,锁消解和Java 8对synchronized的强化外全数的难题。
从源代码详解偏重锁的升级换代最终,看见synchronizer#slow_enter)假如存有市场竞争,会初始化ObjectSynchronizer::inflate方式,展开轻量锁的升级换代(收缩)。
透过ObjectSynchronizer::inflateObjectMonitor::enter方式。
Tips:
关于线程你必须知道的8个难题(中)中提到过该方式。难题是锁升级换代(收缩),但重点不在ObjectSynchronizer::inflate,因此代码分析放在新锐锁源代码分析中。
锁的结构
了解ObjectMonitor::enter的逻辑前,先来看ObjectMonitor的结构:
_header字段存储了Object的markOop,为甚么要这样?因为锁升级换代后没有空间存储Object的markOop了,存储到_header中是为了在退出时能够恢复到加锁前的状态。

Tips:
实际上basicLock也存储了对象的markOop;
EntryList中等待线程来自于cxq移入,或Object.notify唤醒但未执行。
重入的实现
objectMonito#enter方式可以拆成三个部分,首先是市场竞争成功或重入的场景:
重入和升级换代的场景中,都会操作_recursions。_recursions记录了进入ObjectMonitor的次数,解锁时要经历相应次数的退出操作才能顺利完成解锁。
适应性自旋
适应性自旋的部分,ObjectMonitor倒数第二次对“轻量”的追求:
objectMonitor#TrySpin方式是对适应性自旋的支持。Java 1.6后加入,移除默认次数的自旋,将自旋次数的决定权交给JVM。
JVM根据锁上一次自旋情况决定,假如刚刚自旋成功,并且持有锁的线程正在执行,JVM会允许再次尝试自旋。假如该锁的自旋经常失败,那么JVM会直接跳过自旋操作过程。
Tips:
适应性自旋的原码分析放在了新锐锁源代码分析中;
objectMonitor#TryLock非常简单,关键控制技术依旧是CAS。
互斥的实现
到现阶段为止,无论是CAS还是自旋,都是偏重锁和轻量锁中出现过的控制技术,为甚么会让ObjectMonitor背上“新锐”的名声呢?
最终是市场竞争失败的场景:
实际上,进入ObjectMonitor#EnterI后也是先尝试“轻量”的加锁方式:
接来下是新锐的真正实现:
逻辑一目了然,封装ObjectWaiter对象,并加入到cxq队列头部。接着往下执行:
逻辑也不复杂,不断的park-XX:SyncFlags的设置:
当SyncFlags == 0时,synchronized直接挂起线程;
当SyncFlags == 1时,synchronized将线程挂起指定时间。
前者是永久挂起,需要被其它线程唤醒,而后者挂起指定的时间后自动唤醒。
Tips:关于线程你必须知道的8个难题(中)聊到过park和parkEvent,底层是透过pthread_cond_wait和pthread_cond_timedwait实现的。
释放新锐锁
释放新锐锁的源代码和注释非常长,他们省略大部分文本,只看关键部分
重入锁退出
他们知道,重入是不断增加_recursions的计数,那么退出重入的场景就非常简单了:
不断的减少_recursions的计数。
释放和写入
JVM的实现中,当前线程是锁的持有者且没有重入时,首先会释放他们持有的锁,接着将改动写入到内存中,最终还肩负着唤醒下一个线程的责任。先来看释放和写入内存的逻辑:
storeload屏障,对于如下语句:
store1;
storeLoad;
load2
保证store1指令的写入在load2指令执行前,对所有处理器可见。
Tips:volatile中详细解释内存屏障。
唤醒的策略
执行释放锁和写入内存后,只需要唤醒下一个线程来“交接”锁的使用权。但是有两个“等待队列”:cxq和EntryList,该从哪个开始唤醒呢?
Java 11前,根据QMode来选择不同的策略:
QMode == 0,默认策略,将cxq放入EntryList;
QMode == 1,翻转cxq,并放入EntryList;
QMode == 2,直接从cxq中唤醒;
QMode == 3,将cxq移入到EntryList的尾部;
QMode == 4,将cxq移入到EntryList的头部。
不同的策略导致了不同的唤醒顺序,现在你知道为甚么说synchronized是非公平锁了吧?
objectMonitor#ExitEpilog方式就很简单了,初始化的是与park对应的unpark方式,这里就不多说了。
Tips:Java 12的objectMonitor移除了QMode,也就是说只有一种唤醒策略了。
总结
他们对新锐锁做个总结。synchronized的新锐锁是ObjectMonitor,它使用到的关键控制技术有CAS和park。相较于mutex#Monitor来说,它们的本质相同,对park的封装,但ObjectMonitor是做了大量优化的复杂实现。
他们看见了新锐锁是如何实现重入性的,和唤醒策略导致的“不公平”。那么他们常说的synchronized保证了原子性,有序性和可见性,是如何实现的呢?
他们可以先思考下这个难题,下篇文章会做一个全方位的总结,给synchronized收下尾。
好了,那时就到这里了,Bye~~
参考链接
synchronizer#slow_enter
https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/synchronizer.cpp#l339
objectMonitor.hpp
https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/objectMonitor.hpp
basicLoc
https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/basicLock.hpp
objectMonito#enter
https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/objectMonitor.cpp#l265
objectMonitor#29
https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/objectMonitor.cpp#l295
objectMonitor#TrySpin
https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/objectMonitor.cpp#l1869
objectMonitor#TryLock
https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/objectMonitor.cpp#l424
ObjectMonitor#EnterI
https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/objectMonitor.cpp#l442
objectMonitor#ObjectWaite
https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/objectMonitor.hpp#l42
objectMonitor#ExitEpilog
https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/objectMonitor.cpp#l1282
Java 12的objectMonitor
https://hg.openjdk.java.net/jdk-updates/jdk12u/file/390566f1850a/src/hotspot/share/runtime/objectMonitor.cpp#l897
mutex#Monitor
https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/runtime/mutex.hpp#l82