本文为死磕Synchronized底层实现第三篇文章,内容为重量级锁实现。

本系列文章将对HotSpot的synchronized锁实现进行全面分析,内容包括偏向锁、轻量级锁、重量级锁的加锁、解锁、锁升级流程的原理及源码分析,希望给在研究synchronized路上的同学一些帮助。

重量级的膨胀和加锁流程

当出现多个线程同时竞争锁时,会进入到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中分配一批monitoromFreeList中。

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); } ... }
  1. 如果当前是无锁状态、锁重入、当前线程是之前持有轻量级锁的线程则进行简单操作后返回。
  2. 先自旋尝试获得锁,这样做的目的是为了减少执行操作系统同步操作带来的开销
  3. 调用EnterI方法获得锁或阻塞

EnterI方法比较长,在看之前,我们先阐述下其大致原理:

一个ObjectMonitor对象包括这么几个关键字段:cxq(下图中的ContentionList),EntryList ,WaitSet,owner。

其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的链表结构,owner指向持有锁的线程。

当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个ObjectWaiter对象插入到cxq的队列的队首,然后调用park函数挂起当前线程。在linux系统上,park函数底层调用的是gclib库的pthread_cond_wait,JDK的ReentrantLock底层也是用该方法挂起线程的。更多细节可以看我之前的两篇文章:关于同步的一点思考-下linux内核级同步机制–futex

当线程释放锁时,会从cxq或EntryList中挑选一个线程唤醒,被选中的线程叫做Heir presumptive即假定继承人(应该是这样翻译),就是图中的Ready Thread,假定继承人被唤醒后会尝试获得锁,但synchronized是非公平的,所以假定继承人不一定能获得锁(这也是它叫”假定”继承人的原因)。

如果线程获得锁后调用Object#wait方法,则会将线程加入到WaitSet中,当被Object#notify唤醒后,会将线程从WaitSet移动到cxq或EntryList中去。需要注意的是,当调用一个锁对象的waitnotify方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。

synchronizedmonitor锁机制和JDK的ReentrantLockCondition是很相似的,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步:

  1. 将当前线程插入到cxq队列的队首
  2. 然后park当前线程
  3. 当被唤醒后再尝试获得锁

这里需要特别说明的是_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的不同,有不同的处理方式:

  1. QMode = 2且cxq非空:取cxq队列队首的ObjectWaiter对象,调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回,后面的代码不会执行了;
  2. QMode = 3且cxq非空:把cxq队列插入到EntryList的尾部;
  3. QMode = 4且cxq非空:把cxq队列插入到EntryList的头部;
  4. 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的区别:

  1. Synchronized是JVM层次的锁实现,ReentrantLock是JDK层次的锁实现;
  2. Synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过ReentrantLock#isLocked判断;
  3. Synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;
  4. Synchronized是不可以被中断的,而ReentrantLock#lockInterruptibly方法是可以被中断的;
  5. 在发生异常时Synchronized会自动释放锁(由javac编译时自动实现),而ReentrantLock需要开发者在finally块中显示释放锁;
  6. ReentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待指定时长的获取,更加灵活;
  7. Synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得锁(上文有说),而ReentrantLock对于已经在等待的线程一定是先来的线程先获得锁;

End

总的来说Synchronized的重量级锁和ReentrantLock的实现上还是有很多相似的,包括其数据结构、挂起线程方式等等。在日常使用中,如无特殊要求用Synchronized就够了。你深入了解这两者其中一个的实现,了解另外一个或其他锁机制都比较容易,这也是我们常说的技术上的相通性。

欢迎关注我的公众号

面试官都叫好的Synchronized底层实现,这工资开多少一个月?的更多相关文章

  1. 面试官:说一下Synchronized底层实现,锁升级的具体过程?

    面试官:说一下Synchronized底层实现,锁升级的具体过程? 这是我去年7,8月份面试的时候被问的一个面试题,说实话被问到这个问题还是很意外的,感觉这个东西没啥用啊,直到后面被问了一波new O ...

  2. 面试官:都说阻塞 I/O 模型将会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?

    摘要: 原创出处 https://studyidea.cn 「公众号:程序通事 」欢迎关注和转载,保留摘要,谢谢! 使用 Java 阻塞 I/O 模型读取数据,将会导致线程阻塞,线程将会进入休眠,从而 ...

  3. 面试官再问你 HashMap 底层原理,就把这篇文章甩给他看

    前言 HashMap 源码和底层原理在现在面试中是必问的.因此,我们非常有必要搞清楚它的底层实现和思想,才能在面试中对答如流,跟面试官大战三百回合.文章较长,介绍了很多原理性的问题,希望对你有所帮助~ ...

  4. 面试 HTTP ,99% 的面试官都爱问这些问题

    HTTP 和 HTTPS 的区别 HTTP 是一种 超文本传输协议(Hypertext Transfer Protocol),HTTP 是一个在计算机世界里专门在两点之间传输文字.图片.音频.视频等超 ...

  5. 深度分析:面试腾讯,阿里面试官都喜欢问的String源码,看完你学会了吗?

    前言 最近花了两天时间,整理了一下String的源码.这个整理并不全面但是也涵盖了大部分Spring源码中的方法.后续如果有时间还会将剩余的未整理的方法更新到这篇文章中.方便以后的复习和面试使用.如果 ...

  6. 「mysql优化专题」主从复制面试宝典!面试官都没你懂得多!(11)

    内容较多,可先收藏,目录如下: 一.什么是主从复制 二.主从复制的作用(重点) 三.主从复制的原理(重中之重) 四.三步轻松构建主从 五.必问面试题干货分析(最最重要的点) 一.什么是主从复制(技术文 ...

  7. 这些Servlet知识你一定要知道,金九银十大厂面试官都爱问

    前言 Servlet是服务器端的Java应用程序,可以生产动态Web页面.透过JSP执行过程可以知道JSP最终被编译成一个.class文件,查看该文件对应的Java类,发现该Java类继承自org.a ...

  8. 利用递归,反射,注解等,手写Spring Ioc和Di 底层(分分钟喷倒面试官)了解一下

    再我们现在项目中Spring框架是目前各大公司必不可少的技术,而大家都知道去怎么使用Spring ,但是有很多人都不知道SpringIoc底层是如何工作的,而一个开发人员知道他的源码,底层工作原理,对 ...

  9. 面试官最爱的volatile关键字

    在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度,而以volatile关键字作为一个小的切入点,往往可以一问到底,把Java内存模型(JMM),Java并发编程的一些特性 ...

随机推荐

  1. Javase之集合体系之(1)集合顶层类Collection与其迭代器知识

    集合体系之集合顶层类Collection与其迭代器知识 集合的由来:Java是一门面向对象语言,而面向对象语言对事物的描述是通过对象体现的,为了方便对多个对象进行操作,就必须把多个对象进行存储,而要存 ...

  2. SRDC - ORA-1555: Query Duration 0: Checklist of Evidence to Supply (Doc ID 1682704.1)

    SRDC - ORA-1555: Query Duration 0: Checklist of Evidence to Supply (Doc ID 1682704.1) Action Plan 1. ...

  3. protocol buffers 使用方法

    protocol buffers 使用方法 为什么使用 Protocol Buffers 我们接下来要使用的例子是一个非常简单的"地址簿"应用程序,它能从文件中读取联系人详细信息. ...

  4. javascript 生成指定范围内的随机数

    js 生成任意2个区间内的随机整数,js 生成两个数之间的随机数 function random(m,n) { return Math.floor(Math.random() * (n - m)) + ...

  5. Git入门基础教程和SourceTree应用

    目录 一.Git的安装 1.1 图形化界面 1.2 命令行界面 二.本地仓库的创建与提交 2.1 图形化界面 2.1.1 首先在电脑上有一个空白目录 2.1.2 打开SourceTree 2.1.3 ...

  6. LVM 在线扩容磁盘(ubuntu 14.04 server)

    mware workstation 8 或者 vmware vsphere client 6.0( exsi 6.0) (前提:你的服务器需要有已经存在的卷组,才可以添加新的物理卷到卷组,然后再扩容逻 ...

  7. UEFI Install CentOS 7

    bios必须设置u盘为第一启动项 编辑E:\EFI\BOOT\grub.cfg中所有inst.stage2=hd:LABEL=*与卷标名称一致(区分大小写)(linux系统写入镜像无需修改) inst ...

  8. 自动化部署-svn hook触发构建

    目的 之前是通过轮询的形式,2分钟更新一次svn,即时性不高,现在想要实现提交代码时直接触发构建 方案 使用svn的服务器hook,当有代码提交时请求jenkins api实现构建 具体实现 1.je ...

  9. 17.Java基础_初探类的private和public关键字

    package pack1; public class Student { // 成员变量 private String name; private int age; // get/set方法 pub ...

  10. 初学JavaScript正则表达式(六)

    JavaScript预定义类 ab+数字+任意字符 ab[0-9][^\r\n] 等价于 ab\d. '@123@abc@'.replace(/@./g,'Q') Q23Qbc@ 将"@加任 ...