什么是synchronized的重量级锁?

2022-12-31 0 636

什么是synchronized的重量级锁?

他们好,我是王自强。高度关注王自强,一同聊控制技术,聊格斗游戏,从白领日常生活谈及历有。

鸽了很久是给他们找出了出格的理据–羊了。虽说流鼻血那两天吗很郁闷,显然不该下床,顺利完成日常日常生活组织工作都早已耗尽了全数的气力,显然没经心力写诗。

嘿嘿,那时他们竭尽全力自学synchronized的升级换代操作过程,现阶段只剩最终一步棋了:轻量锁->新锐锁。

透过那时的文本,期望能协助他们答疑synchronized都问啥?中除锁粗化,锁消解和Java 8对synchronized的强化外全数的难题。

从源代码详解偏重锁的升级换代最终,看见synchronizer#slow_enter)假如存有市场竞争,会初始化ObjectSynchronizer::inflate方式,展开轻量锁的升级换代(收缩)。

voidObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS){ ……ObjectSynchronizer::inflate(THREAD, obj(), inflate_cause_monitor_enter)->enter(THREAD);}

透过ObjectSynchronizer::inflateObjectMonitor::enter方式。

Tips

关于线程你必须知道的8个难题(中)中提到过该方式。难题是锁升级换代(收缩),但重点不在ObjectSynchronizer::inflate,因此代码分析放在新锐锁源代码分析中。

锁的结构

了解ObjectMonitor::enter的逻辑前,先来看ObjectMonitor的结构:

classObjectMonitor {private:// 保存与ObjectMonitor关联Object的markOopvolatilemarkOop _header;// 与ObjectMonitor关联的Objectvoid* volatile _object;protected:// ObjectMonitor的拥有者void * volatile_owner;// 递归计数volatileintptr_t _recursions;// 等待线程队列,cxq移入/Object.notify唤醒的线程 ObjectWaiter * volatile_EntryList;private:// 市场竞争队列 ObjectWaiter * volatile _cxq;// ObjectMonitor的维护线程 Thread * volatile _Responsible;protected:// 线程挂起队列(初始化Object.wait) ObjectWaiter * volatile _WaitSet;}

_header字段存储了Object的markOop,为甚么要这样?因为锁升级换代后没有空间存储Object的markOop了,存储到_header中是为了在退出时能够恢复到加锁前的状态

什么是synchronized的重量级锁?

Tips

实际上basicLock也存储了对象的markOop;

EntryList中等待线程来自于cxq移入,或Object.notify唤醒但未执行。

重入的实现

objectMonito#enter方式可以拆成三个部分,首先是市场竞争成功或重入的场景

Thread *const Self = THREAD;// CAS抢占锁,假如失败则返回_ownervoid * cur = Atomic::cmpxchg(Self, &_owner, (void*)NULL);if (cur == NULL) {// CAS抢占锁成功直接返回return;}// CAS失败场景// 新锐锁重入if (cur == Self) {// 递归计数+1 _recursions++;return;}// 当前线程是否曾持有轻量锁// 可以看做是特殊的重入if (Self->is_lock_owned ((address)cur)) {// 递归计数器置为1 _recursions = 1; _owner = Self;return;}

重入和升级换代的场景中,都会操作_recursions_recursions记录了进入ObjectMonitor的次数,解锁时要经历相应次数的退出操作才能顺利完成解锁。

适应性自旋

适应性自旋的部分,ObjectMonitor倒数第二次对“轻量”的追求

// 尝试自旋来市场竞争锁Self->_Stalled =intptr_t(this);if (Knob_SpinEarly && TrySpin (Self) > 0) { Self->_Stalled = 0;return;}

objectMonitor#TrySpin方式是对适应性自旋的支持。Java 1.6后加入,移除默认次数的自旋,将自旋次数的决定权交给JVM。

JVM根据锁上一次自旋情况决定,假如刚刚自旋成功,并且持有锁的线程正在执行,JVM会允许再次尝试自旋。假如该锁的自旋经常失败,那么JVM会直接跳过自旋操作过程。

Tips

适应性自旋的原码分析放在了新锐锁源代码分析中;

objectMonitor#TryLock非常简单,关键控制技术依旧是CAS。

互斥的实现

到现阶段为止,无论是CAS还是自旋,都是偏重锁和轻量锁中出现过的控制技术,为甚么会让ObjectMonitor背上“新锐”的名声呢?

最终是市场竞争失败的场景:

// 此处省略了修改当前线程状态的代码for (;;) { EnterI(THREAD);}

实际上,进入ObjectMonitor#EnterI后也是先尝试“轻量”的加锁方式:

voidObjectMonitor::EnterI(TRAPS){if (TryLock (Self) > 0) {return; }if (TrySpin (Self) > 0) {return; }}

接来下是新锐的真正实现:

// 将当前线程(Self)封装为ObjectWaiter的nodeObjectWaiter node(Self);Self->_ParkEvent->reset();node._prev = (ObjectWaiter *) 0xBAD;node.TState = ObjectWaiter::TS_CXQ;// 将node插入到cxq的头部ObjectWaiter * nxt;for (;;) {node._next = nxt = _cxq;if (Atomic::cmpxchg(&node, &_cxq, nxt) == nxt)break;if (TryLock (Self) > 0) {return; }}

逻辑一目了然,封装ObjectWaiter对象,并加入到cxq队列头部。接着往下执行:

// 将当前线程(Self)设置为当前ObjectMonitor的维护线程(_Responsible)// SyncFlags的默认值为0,可以透过-XX:SyncFlags设置if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) { Atomic::replace_if_null(Self, &_Responsible);}for (;;) {// 尝试设置_Responsibleif ((SyncFlags & 2) && _Responsible ==NULL) { Atomic::replace_if_null(Self, &_Responsible); }// park当前线程if(_Responsible == Self || (SyncFlags &1)) { Self->_ParkEvent->park((jlong) recheckInterval); // 简单的退避算法,recheckInterval从1ms开始 recheckInterval *= 8;if (recheckInterval > MAX_RECHECK_INTERVAL) {recheckInterval = MAX_RECHECK_INTERVAL; } } else { Self->_ParkEvent->park(); }if (TryLock(Self) > 0)break;if ((Knob_SpinAfterFutile & 1) && TrySpin(Self) >0) break;if (_succ == Self) _succ = NULL;}

逻辑也不复杂,不断的park-XX:SyncFlags的设置:

SyncFlags == 0时,synchronized直接挂起线程;

SyncFlags == 1时,synchronized将线程挂起指定时间。

前者是永久挂起,需要被其它线程唤醒,而后者挂起指定的时间后自动唤醒

Tips关于线程你必须知道的8个难题(中)聊到过parkparkEvent,底层是透过pthread_cond_waitpthread_cond_timedwait实现的。

释放新锐锁

释放新锐锁的源代码和注释非常长,他们省略大部分文本,只看关键部分

重入锁退出

他们知道,重入是不断增加_recursions的计数,那么退出重入的场景就非常简单了:

voidObjectMonitor::exit(bool not_suspended, TRAPS){ Thread * const Self = THREAD;// 第二次持有锁时,_recursions == 1// 重入场景只需要退出重入即可if (_recursions != 0) { _recursions–;return; } …..}

不断的减少_recursions的计数。

释放和写入

JVM的实现中,当前线程是锁的持有者且没有重入时,首先会释放他们持有的锁,接着将改动写入到内存中,最终还肩负着唤醒下一个线程的责任。先来看释放和写入内存的逻辑:

// 置空锁的持有者OrderAccess::release_store(&_owner, (void*)NULL);// storeload屏障,OrderAccess::storeload();// 没有市场竞争线程则直接退出if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) { TEVENT(Inflated exit – simple egress);return;}

storeload屏障,对于如下语句:

store1;

storeLoad;

load2

保证store1指令的写入在load2指令执行前,对所有处理器可见。

Tipsvolatile中详细解释内存屏障。

唤醒的策略

执行释放锁和写入内存后,只需要唤醒下一个线程来“交接”锁的使用权。但是有两个“等待队列”:cxqEntryList,该从哪个开始唤醒呢?

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方式,这里就不多说了。

TipsJava 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

举报/反馈

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务