https://blog.51cto.com/14440216/2427416
https://www.zhihu.com/question/52116998/answer/133400077
JVM内存模型:顺序性 原子性 可见性
synchronized的底层实现主要依靠Lock-Free的队列。
基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。
synchronized实现何时使用了自旋锁?
在线程进入ContentionList时,也即第一步操作前。线程在进入等待队列时 首先进行自旋尝试获得锁,如果不成功再进入等待队
列。这对那些已经在等待队列中的线程来说,稍微显得不公平。还有一个不公平的地方是自旋线程可能会抢占了 Ready线程的锁。
自旋锁由每个监视对象维护,每个监视对象一个。
说明:
- 1.1 ContentionList 虚拟队列:后进先出,队头插入,队尾移出。一个后进先出(LIFO)的队列,每次新加入Node时都会在队头进行, 通过CAS改变第一个节点的的指针为新增节点,同时设置新增节点的next指向后续节点,而取得操作则发生在队尾。只有Owner线程才能从队尾取元素,也即线程出列操作无争用。
- 1.2 ContentionList会被线程并发访问,为了降低对 ContentionList队尾的争用,而建立EntryList。Owner线程在unlock时会从ContentionList中迁移线程到 EntryList,并会指定EntryList中的某个线程(一般为Head)为Ready(OnDeck)线程。Owner线程并不是把锁传递给 OnDeck线程,只是把竞争锁的权利交给OnDeck,OnDeck线程需要重新竞争锁。这样做虽然牺牲了一定的公平性,但极大的提高了整体吞吐量,在 Hotspot中把OnDeck的选择行为称之为“竞争切换”。 OnDeck线程获得锁后即变为owner线程,无法获得锁则会依然留在EntryList中,考虑到公平性,在EntryList中的位置不 发生变化(依然在队头)。如果Owner线程被wait方法阻塞,则转移到WaitSet队列;如果在某个时刻被notify/notifyAll唤醒, 则再次转移到EntryList。
- 1.3 现在几乎所有的锁都是可重入的,也即已经获得锁的线程可以多次锁住/解锁监视对象,按照之前的HotSpot设计,每次加锁/解锁都会涉及到一些CAS操 作(比如对等待队列的CAS操作),CAS操作会延迟本地调用,因此偏向锁的想法是一旦线程第一次获得了监视对象,之后让监视对象“偏向”这个 线程,之后的多次调用则可以避免CAS操作,说白了就是置个变量,如果发现为true则无需再走各种加锁/解锁流程。
线程状态切换
sleep() 和 wait() 有什么区别?
1、sleep就是正在执行的线程主动让出cpu,cpu去执行其他线程,在sleep指定的时间过后,cpu才会回到这个线程上继续往下执行
1.2、如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行。
2、wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行
2.2、只有其他线程调用了notify方法,调用wait方法的线程就会解除wait状态和程序可以再次得到锁后继续向下运行。
【注意】notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁
【总结】notify是告诉wait()的线程什么时候可以去继续去申请锁了
wait方法不能在没有synchronized修饰的代码中执行。
CPU做的事情
调度、计算(执行代码)
偏向锁
加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距
如果线程间存在锁竞争,会带来额外的锁撤销的消耗
适用于只有一个线程访问同步块场景【只有一个线程】
轻量级锁[使用自旋]
竞争的线程不会阻塞,提高了程序的响应速度 【没有多线程竞争的前提下,减少传统重量级锁使用产生的性能消耗。】线程交替执行
如果始终得不到锁竞争的线程使用自旋会消耗CPU
追求响应时间,锁占用时间很短【线程交替执行,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。】
重量级锁
线程竞争不使用自旋,不会消耗CPU
线程阻塞,响应时间缓慢
追求吞吐量,锁占用时间较长
轻量级锁:
通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。
如果替换成功,整个同步过程就完成了。
如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。
CAS操作尝试把线程中复制的Displaced Mark Word对象替换成当前的MarkWord
轻量级锁
“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。
但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,
它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。
在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,
如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
轻量级锁的加锁过程
1、在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),
虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,
官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如图一所示。
2、拷贝对象头中的Mark Word复制到锁记录中。
拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。
如果更新成功,则执行步骤(4),否则执行步骤(5)。
3、如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark
Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图二所示。
4、如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,
那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,
Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。而当前线程便尝试使用自旋来获取锁,
自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。
重量级锁:
同步阻塞,系统调用,牵涉到内核态。由内核进行仲裁。
重量级的膨胀和加锁流程
当出现多个线程同时竞争锁时,会进入到synchronizer.cpp#slow_enter
方法
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
// 如果是无锁状态
if (mark->is_neutral()) {
lock->set_displaced_header(mark);
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
return ;
}
// Fall through to inflate() ...
} else
// 如果是轻量级锁重入
if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
assert(lock != mark->locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
lock->set_displaced_header(NULL);
return;
}
...
// 这时候需要膨胀为重量级锁,膨胀前,设置Displaced Mark Word为一个特殊值,代表该锁正在用一个重量级锁的monitor
lock->set_displaced_header(markOopDesc::unused_mark());
//先调用inflate膨胀为重量级锁,该方法返回一个ObjectMonitor对象,然后调用其enter方法
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
在inflate
中完成膨胀过程。
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
...
for (;;) {
const markOop mark = object->mark() ;
assert (!mark->has_bias_pattern(), "invariant") ;
// mark是以下状态中的一种:
// * Inflated(重量级锁状态) - 直接返回
// * Stack-locked(轻量级锁状态) - 膨胀
// * INFLATING(膨胀中) - 忙等待直到膨胀完成
// * Neutral(无锁状态) - 膨胀
// * BIASED(偏向锁) - 非法状态,在这里不会出现
// CASE: inflated
if (mark->has_monitor()) {
// 已经是重量级锁状态了,直接返回
ObjectMonitor * inf = mark->monitor() ;
...
return inf ;
}
// CASE: inflation in progress
if (mark == markOopDesc::INFLATING()) {
// 正在膨胀中,说明另一个线程正在进行锁膨胀,continue重试
TEVENT (Inflate: spin while INFLATING) ;
// 在该方法中会进行spin/yield/park等操作完成自旋动作
ReadStableMark(object) ;
continue ;
}
if (mark->has_locker()) {
// 当前轻量级锁状态,先分配一个ObjectMonitor对象,并初始化值
ObjectMonitor * m = omAlloc (Self) ;
m->Recycle();
m->_Responsible = NULL ;
m->OwnerIsThread = 0 ;
m->_recursions = 0 ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class
// 将锁对象的mark word设置为INFLATING (0)状态
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
if (cmp != mark) {
omRelease (Self, m, true) ;
continue ; // Interference -- just retry
}
// 栈中的displaced mark word
markOop dmw = mark->displaced_mark_helper() ;
assert (dmw->is_neutral(), "invariant") ;
// 设置monitor的字段
m->set_header(dmw) ;
// owner为Lock Record
m->set_owner(mark->locker());
m->set_object(object);
...
// 将锁对象头设置为重量级锁状态
object->release_set_mark(markOopDesc::encode(m));
...
return m ;
}
// CASE: neutral
// 分配以及初始化ObjectMonitor对象
ObjectMonitor * m = omAlloc (Self) ;
// prepare m for installation - set monitor to initial state
m->Recycle();
m->set_header(mark);
// owner为NULL
m->set_owner(NULL);
m->set_object(object);
m->OwnerIsThread = 1 ;
m->_recursions = 0 ;
m->_Responsible = NULL ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // consider: keep metastats by type/class
// 用CAS替换对象头的mark word为重量级锁状态
if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
// 不成功说明有另外一个线程在执行inflate,释放monitor对象
m->set_object (NULL) ;
m->set_owner (NULL) ;
m->OwnerIsThread = 0 ;
m->Recycle() ;
omRelease (Self, m, true) ;
m = NULL ;
continue ;
// interference - the markword changed - just retry.
// The state-transitions are one-way, so there's no chance of
// live-lock -- "Inflated" is an absorbing state.
}
...
return m ;
}
}
inflate
中是一个for循环,主要是为了处理多线程同时调用inflate的情况。然后会根据锁对象的状态进行不同的处理:
1.已经是重量级状态,说明膨胀已经完成,直接返回
2.如果是轻量级锁则需要进行膨胀操作
3.如果是膨胀中状态,则进行忙等待
4.如果是无锁状态则需要进行膨胀操作
其中轻量级锁和无锁状态需要进行膨胀操作,轻量级锁膨胀流程如下:
1.调用omAlloc
分配一个ObjectMonitor
对象(以下简称monitor),在omAlloc
方法中会先从线程私有的monitor
集合omFreeList
中分配对象,如果omFreeList
中已经没有monitor
对象,则从JVM全局的gFreeList
中分配一批monitor
到omFreeList
中。
2.初始化monitor
对象
3.将状态设置为膨胀中(INFLATING)状态
4.设置monitor
的header字段为displaced mark word
,owner字段为Lock Record
,obj字段为锁对象
5.设置锁对象头的mark word
为重量级锁状态,指向第一步分配的monitor
对象
无锁状态下的膨胀流程如下:
1.调用omAlloc
分配一个ObjectMonitor
对象(以下简称monitor)
2.初始化monitor
对象
3.设置monitor
的header字段为 mark word
,owner字段为null
,obj字段为锁对象
4.设置锁对象头的mark word
为重量级锁状态,指向第一步分配的monitor
对象
至于为什么轻量级锁需要一个膨胀中(INFLATING)状态,代码中的注释是:
// Why do we CAS a 0 into the mark-word instead of just CASing the
// mark-word from the stack-locked value directly to the new inflated state?
// Consider what happens when a thread unlocks a stack-locked object.
// It attempts to use CAS to swing the displaced header value from the
// on-stack basiclock back into the object header. Recall also that the
// header value (hashcode, etc) can reside in (a) the object header, or
// (b) a displaced header associated with the stack-lock, or (c) a displaced
// header in an objectMonitor. The inflate() routine must copy the header
// value from the basiclock on the owner's stack to the objectMonitor, all
// the while preserving the hashCode stability invariants. If the owner
// decides to release the lock while the value is 0, the unlock will fail
// and control will eventually pass from slow_exit() to inflate. The owner
// will then spin, waiting for the 0 value to disappear. Put another way,
// the 0 causes the owner to stall if the owner happens to try to
// drop the lock (restoring the header from the basiclock to the object)
// while inflation is in-progress. This protocol avoids races that might
// would otherwise permit hashCode values to change or "flicker" for an object.
// Critically, while object->mark is 0 mark->displaced_mark_helper() is stable.
// 0 serves as a "BUSY" inflate-in-progress indicator.
我没太看懂,有知道的同学可以指点下~
膨胀完成之后,会调用enter
方法获得锁
void ATTR ObjectMonitor::enter(TRAPS) {
Thread * const Self = THREAD ;
void * cur ;
// owner为null代表无锁状态,如果能CAS设置成功,则当前线程直接获得锁
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (cur == NULL) {
...
return ;
}
// 如果是重入的情况
if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions ++ ;
return ;
}
// 当前线程是之前持有轻量级锁的线程。由轻量级锁膨胀且第一次调用enter方法,那cur是指向Lock Record的指针
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
// 重入计数重置为1
_recursions = 1 ;
// 设置owner字段为当前线程(之前owner是指向Lock Record的指针)
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
...
// 在调用系统的同步操作之前,先尝试自旋获得锁
if (Knob_SpinEarly && TrySpin (Self) > 0) {
...
//自旋的过程中获得了锁,则直接返回
Self->_Stalled = 0 ;
return ;
}
...
{
...
for (;;) {
jt->set_suspend_equivalent();
// 在该方法中调用系统同步操作
EnterI (THREAD) ;
...
}
Self->set_current_pending_monitor(NULL);
}
...
}
- 如果当前是无锁状态、锁重入、当前线程是之前持有轻量级锁的线程则进行简单操作后返回。
- 先自旋尝试获得锁,这样做的目的是为了减少执行操作系统同步操作带来的开销
- 调用
EnterI
方法获得锁或阻塞
EnterI
方法比较长,在看之前,我们先阐述下其大致原理:
一个ObjectMonitor
对象包括这么几个关键字段:cxq(下图中的ContentionList),EntryList ,WaitSet,owner。
其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的链表结构,owner指向持有锁的线程。
当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个ObjectWaiter
对象插入到cxq的队列的队首,然后调用park
函数挂起当前线程。
在linux系统上,park
函数底层调用的是gclib库的pthread_cond_wait
,JDK的ReentrantLock
底层也是用该方法挂起线程的。
【
#include <pthread.h>
pthread_mutex_t mutex;pthread_cond_t mwait;
bool exit_sign;void init() {
pthread_mutex_init(mutex, NULL); pthread_cond_init(mwait, NULL);
}void relese() {
exit_sign = 1;
pthread_mutex_unlock(mutex);
pthread_cond_signal(mwait);
pthread_mutex_destroy(mutex); pthread_cond_destroy(mwait);
}void pushD(D *data) {
pthread_mutex_lock(&D_mutex);
if (D_queue.size() >= maxQueueSize) {
delete (D_queue.front());
D_queue.pop(); }
D_queue.push(data);
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&mwait);
return 0;
}D* popD() {
pthread_mutex_lock(&mutex);
D *d = NULL;
while (D_queue.size() <= 0 && !exit_sign) {
pthread_cond_wait(&mwait, &mutex);
} if (exit_sign) {
return NULL;
}
d = D_queue.front();
D_queue.pop();
pthread_mutex_unlock(&mutex);
return d;
}
】
当线程释放锁时,会从cxq或EntryList中挑选一个线程唤醒,被选中的线程叫做Heir presumptive
即假定继承人(应该是这样翻译),就是图中的Ready Thread
,假定继承人被唤醒后会尝试获得锁,但synchronized
是非公平的,所以假定继承人不一定能获得锁(这也是它叫”假定”继承人的原因)。
如果线程获得锁后调用Object#wait
方法,则会将线程加入到WaitSet中,当被Object#notify
唤醒后,会将线程从WaitSet移动到cxq或EntryList中去。需要注意的是,当调用一个锁对象的wait
或notify
方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。
synchronized
的monitor
锁机制和JDK的ReentrantLock
与Condition
是很相似的,ReentrantLock
也有一个存放等待获取锁线程的链表,Condition
也有一个类似WaitSet
的集合用来存放调用了await
的线程。如果你之前对ReentrantLock
有深入了解,那理解起monitor
应该是很简单。
回到代码上,开始分析EnterI
方法:
void ATTR ObjectMonitor::EnterI (TRAPS) {
Thread * Self = THREAD ;
...
// 尝试获得锁
if (TryLock (Self) > 0) {
...
return ;
}
DeferredInitialize () ;
// 自旋
if (TrySpin (Self) > 0) {
...
return ;
}
...
// 将线程封装成node节点中
ObjectWaiter node(Self) ;
Self->_ParkEvent->reset() ;
node._prev = (ObjectWaiter *) 0xBAD ;
node.TState = ObjectWaiter::TS_CXQ ;
// 将node节点插入到_cxq队列的头部,cxq是一个单向链表
ObjectWaiter * nxt ;
for (;;) {
node._next = nxt = _cxq ;
if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
// CAS失败的话 再尝试获得锁,这样可以降低插入到_cxq队列的频率
if (TryLock (Self) > 0) {
...
return ;
}
}
// SyncFlags默认为0,如果没有其他等待的线程,则将_Responsible设置为自己
if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
TEVENT (Inflated enter - Contention) ;
int nWakeups = 0 ;
int RecheckInterval = 1 ;
for (;;) {
if (TryLock (Self) > 0) break ;
assert (_owner != Self, "invariant") ;
...
// park self
if (_Responsible == Self || (SyncFlags & 1)) {
// 当前线程是_Responsible时,调用的是带时间参数的park
TEVENT (Inflated enter - park TIMED) ;
Self->_ParkEvent->park ((jlong) RecheckInterval) ;
// Increase the RecheckInterval, but clamp the value.
RecheckInterval *= 8 ;
if (RecheckInterval > 1000) RecheckInterval = 1000 ;
} else {
//否则直接调用park挂起当前线程
TEVENT (Inflated enter - park UNTIMED) ;
Self->_ParkEvent->park() ;
}
if (TryLock(Self) > 0) break ;
...
if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
...
// 在释放锁时,_succ会被设置为EntryList或_cxq中的一个线程
if (_succ == Self) _succ = NULL ;
// Invariant: after clearing _succ a thread *must* retry _owner before parking.
OrderAccess::fence() ;
}
// 走到这里说明已经获得锁了
assert (_owner == Self , "invariant") ;
assert (object() != NULL , "invariant") ;
// 将当前线程的node从cxq或EntryList中移除
UnlinkAfterAcquire (Self, &node) ;
if (_succ == Self) _succ = NULL ;
if (_Responsible == Self) {
_Responsible = NULL ;
OrderAccess::fence();
}
...
return ;
}
主要步骤有3步:
- 将当前线程插入到cxq队列的队首
- 然后park当前线程
- 当被唤醒后再尝试获得锁
这里需要特别说明的是_Responsible
和_succ
两个字段的作用:
当竞争发生时,选取一个线程作为_Responsible
,_Responsible
线程调用的是有时间限制的park
方法,其目的是防止出现搁浅
现象。
_succ
线程是在线程释放锁是被设置,其含义是Heir presumptive
,也就是我们上面说的假定继承人。
重量级锁的释放
重量级锁释放的代码在ObjectMonitor::exit
:
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * Self = THREAD ;
// 如果_owner不是当前线程
if (THREAD != _owner) {
// 当前线程是之前持有轻量级锁的线程。由轻量级锁膨胀后还没调用过enter方法,_owner会是指向Lock Record的指针。
if (THREAD->is_lock_owned((address) _owner)) {
assert (_recursions == 0, "invariant") ;
_owner = THREAD ;
_recursions = 0 ;
OwnerIsThread = 1 ;
} else {
// 异常情况:当前不是持有锁的线程
TEVENT (Exit - Throw IMSX) ;
assert(false, "Non-balanced monitor enter/exit!");
if (false) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
return;
}
}
// 重入计数器还不为0,则计数器-1后返回
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
TEVENT (Inflated exit - recursive) ;
return ;
}
// _Responsible设置为null
if ((SyncFlags & 4) == 0) {
_Responsible = NULL ;
}
...
for (;;) {
assert (THREAD == _owner, "invariant") ;
// Knob_ExitPolicy默认为0
if (Knob_ExitPolicy == 0) {
// code 1:先释放锁,这时如果有其他线程进入同步块则能获得锁
OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock
OrderAccess::storeload() ; // See if we need to wake a successor
// code 2:如果没有等待的线程或已经有假定继承人
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
TEVENT (Inflated exit - simple egress) ;
return ;
}
TEVENT (Inflated exit - complex egress) ;
// code 3:要执行之后的操作需要重新获得锁,即设置_owner为当前线程
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
return ;
}
TEVENT (Exit - Reacquired) ;
}
...
ObjectWaiter * w = NULL ;
// code 4:根据QMode的不同会有不同的唤醒策略,默认为0
int QMode = Knob_QMode ;
if (QMode == 2 && _cxq != NULL) {
// QMode == 2 : cxq中的线程有更高优先级,直接唤醒cxq的队首线程
w = _cxq ;
assert (w != NULL, "invariant") ;
assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
ExitEpilog (Self, w) ;
return ;
}
if (QMode == 3 && _cxq != NULL) {
// 将cxq中的元素插入到EntryList的末尾
w = _cxq ;
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL , "invariant") ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
// Append the RATs to the EntryList
// TODO: organize EntryList as a CDLL so we can locate the tail in constant-time.
ObjectWaiter * Tail ;
for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
if (Tail == NULL) {
_EntryList = w ;
} else {
Tail->_next = w ;
w->_prev = Tail ;
}
// Fall thru into code that tries to wake a successor from EntryList
}
if (QMode == 4 && _cxq != NULL) {
// 将cxq插入到EntryList的队首
w = _cxq ;
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL , "invariant") ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
// Prepend the RATs to the EntryList
if (_EntryList != NULL) {
q->_next = _EntryList ;
_EntryList->_prev = q ;
}
_EntryList = w ;
// Fall thru into code that tries to wake a successor from EntryList
}
w = _EntryList ;
if (w != NULL) {
// 如果EntryList不为空,则直接唤醒EntryList的队首元素
assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
// EntryList为null,则处理cxq中的元素
w = _cxq ;
if (w == NULL) continue ;
// 因为之后要将cxq的元素移动到EntryList,所以这里将cxq字段设置为null
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
TEVENT (Inflated exit - drain cxq into EntryList) ;
assert (w != NULL , "invariant") ;
assert (_EntryList == NULL , "invariant") ;
if (QMode == 1) {
// QMode == 1 : 将cxq中的元素转移到EntryList,并反转顺序
ObjectWaiter * s = NULL ;
ObjectWaiter * t = w ;
ObjectWaiter * u = NULL ;
while (t != NULL) {
guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
t->TState = ObjectWaiter::TS_ENTER ;
u = t->_next ;
t->_prev = u ;
t->_next = s ;
s = t;
t = u ;
}
_EntryList = s ;
assert (s != NULL, "invariant") ;
} else {
// QMode == 0 or QMode == 2‘
// 将cxq中的元素转移到EntryList
_EntryList = w ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
}
// _succ不为null,说明已经有个继承人了,所以不需要当前线程去唤醒,减少上下文切换的比率
if (_succ != NULL) continue;
w = _EntryList ;
// 唤醒EntryList第一个元素
if (w != NULL) {
guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
}
}
在进行必要的锁重入判断以及自旋优化后,进入到主要逻辑:
code 1
设置owner为null,即释放锁,这个时刻其他的线程能获取到锁。这里是一个非公平锁的优化;
code 2
如果当前没有等待的线程则直接返回就好了,因为不需要唤醒其他线程。或者如果说succ不为null,代表当前已经有个”醒着的”继承人线程,那当前线程不需要唤醒任何线程;
code 3
当前线程重新获得锁,因为之后要操作cxq和EntryList队列以及唤醒线程;
code 4
根据QMode的不同,会执行不同的唤醒策略;
根据QMode的不同,有不同的处理方式:
- QMode = 2且cxq非空:取cxq队列队首的ObjectWaiter对象,调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回,后面的代码不会执行了;
- QMode = 3且cxq非空:把cxq队列插入到EntryList的尾部;
- QMode = 4且cxq非空:把cxq队列插入到EntryList的头部;
- QMode = 0:暂时什么都不做,继续往下看;
只有QMode=2的时候会提前返回,等于0、3、4的时候都会继续往下执行:
1.如果EntryList的首元素非空,就取出来调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回;
2.如果EntryList的首元素为空,就将cxq的所有元素放入到EntryList中,然后再从EntryList中取出来队首元素执行ExitEpilog方法,然后立即返回;
QMode默认为0,结合上面的流程我们可以看这么个demo:
public class SyncDemo {
public static void main(String[] args) {
SyncDemo syncDemo1 = new SyncDemo();
syncDemo1.startThreadA();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
syncDemo1.startThreadB();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
syncDemo1.startThreadC();
}
final Object lock = new Object();
public void startThreadA() {
new Thread(() -> {
synchronized (lock) {
System.out.println("A get lock");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A release lock");
}
}, "thread-A").start();
}
public void startThreadB() {
new Thread(() -> {
synchronized (lock) {
System.out.println("B get lock");
}
}, "thread-B").start();
}
public void startThreadC() {
new Thread(() -> {
synchronized (lock) {
System.out.println("C get lock");
}
}, "thread-C").start();
}
}
默认策略下,在A释放锁后一定是C线程先获得锁。因为在获取锁时,是将当前线程插入到cxq的头部,而释放锁时,默认策略是:如果EntryList为空,则将cxq中的元素按原有顺序插入到到EntryList,并唤醒第一个线程。也就是当EntryList为空时,是后来的线程先获取锁。这点JDK中的Lock机制是不一样的。
Synchronized和ReentrantLock的区别
原理弄清楚了,顺便总结了几点Synchronized和ReentrantLock的区别:
- Synchronized是JVM层次的锁实现,ReentrantLock是JDK层次的锁实现;
- Synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过
ReentrantLock#isLocked
判断; - Synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;
- Synchronized是不可以被中断的,而
ReentrantLock#lockInterruptibly
方法是可以被中断的; - 在发生异常时Synchronized会自动释放锁(由javac编译时自动实现),而ReentrantLock需要开发者在finally块中显示释放锁;
- ReentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待指定时长的获取,更加灵活;
- Synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得锁(上文有说),而ReentrantLock对于已经在等待的线程一定是先来的线程先获得锁;
End
总的来说Synchronized的重量级锁和ReentrantLock的实现上还是有很多相似的,包括其数据结构、挂起线程方式等等。在日常使用中,如无特殊要求用Synchronized就够了。你深入了解这两者其中一个的实现,了解另外一个或其他锁机制都比较容易,这也是我们常说的技术上的相通性。