持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
介绍synchronized原理的时候,很多文章中上来就讲锁优化,或者把monitorenter和锁优化一下次全部讲了,其实这样的逻辑是混乱的,因为我们在字节码层面,只能看到加synchronized和不加synchronized代码块有四个区别(详见字节码层面原理部分),无法看到锁优化的任何内容,我们其实是从字节码中定位到monitorenter/monitorexit,然后从HotSpot源码中monitorenter和monitorexit部分源码才能看到,原来synchronized是通过改变对象的对象头内容来实现加锁和释放锁的,这期间可能要经历偏向锁->轻量级锁->重量级锁的过程,而早期synchronized只有没有偏向锁和轻量级锁,只有重量级锁。从只有重量级锁到偏向锁->轻量级锁->重量级锁这个改进叫锁优化。
本文先介绍synchronized的使用方法,接着,从demo的字节码原理出发到HosSpot源码,抽丝剥茧剖析synchronized底层的锁优化原理实现。
使用
同步指的是多个线程并发访问共享数据的时候,保证共享数据在同一时刻只能被一个线程使用,而互斥是同步的一种方式,最基本的互斥手段就是synchronized
同步代码块
synchronized(this|object) 作用于对象。synchronized(类.class) 作用于class
public void func() {
synchronized (this) {
// ...
}
}
同步静态方法
作用于当前class
public synchronized static void func() {
// ...
}
同步实例方法
作用于当前实例
public synchronized void func () {
// ...
}
理解与说明:
- 先看同步的是什么。静态方法的时候锁为类,普通方法的时候锁为实例,代码块的时候锁为synchronized括号里面的对象,如果是this或者Object,那么锁就是实例,如果是类.class,锁就是类。
- 当确定锁作用于什么之后,需要明确,当作用于类的时候,线程A和线程B分别调用类中的方法A和方法B,两个线程有一个被阻塞,另一个执行。
- 当作用于对象的时候,如果线程A和线程B分别作用于同一个对象的方法A和方法B,那么两个线程有一个被阻塞,另一个执行。当两个线程作用于不同对象的两个方法时就不会被阻塞。
- 当作用于对象的时候,两个线程调用的是同一个对象的同步代码块时,两个线程会同步。当作用于不同对象的同步代码块的时候,就不会同步。当作用于整个类的时候,两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。
- 构造方法本身就是线程安全的,所以不能也不需要用synchronized修饰。
原理
JDK早期,Synchronized是重量级锁,也就是申请锁资源必须通过内核,涉及到用户态到内核态的转换,这需要经过一个中断的调用,申请完之后还需要从内核态返回到用户态。所以在JDK 1.6之后进行了优化,也就是synchronized上锁时只需要在用户态就可以实现,而不需要进行用户态和内核态之间的切换。
字节码层面原理
同步代码块
以下面代码为例
public class Counter {
private int count = 0;
public void increase(){
++count;
}
public int getCount(){
return count;
}
}
Count类的increase方法并非线程安全的,对应的字节码为
0 aload_0
1 dup
2 getfield #2 <class01/Counter.count : I>
5 iconst_1
6 iadd
7 putfield #2 <class01/Counter.count : I>
10 return
这里2-7行中,getfield #2指令获取count的值,iconst_1和 iadd将取出的值加1,然后putfield #2指令将之后的值写回到count字段中,这是一个典型的read-modify-write模式。如果同时有两个线程调用increase方法,各自通过getfield #2 获得了count的值,随后执行加1操作,最后各自将更新的值返回到count中,那么count只被加1,丢失一次加1操作。
此时可以通过加Synchronized关键字修饰increase方法。
public class Counter {
private int count = 0;
public void increase(){
synchronized(this) {
++count;
}
}
public int getCount(){
return count;
}
}
对应的字节码为
0 aload_0
1 dup
2 astore_1
3 monitorenter
4 aload_0
5 dup
6 getfield #2 <class01/Counter.count : I>
9 iconst_1
10 iadd
11 putfield #2 <class01/Counter.count : I>
14 aload_1
15 monitorexit
16 goto 24 (+8)
19 astore_2
20 aload_1
21 monitorexit
22 aload_2
23 athrow
24 return
- 0-2行:将this对象引入栈中,使用dup指令复制栈顶元素,并把它保存在局部变量表1的位置,现在站上剩下this对象引用
- 第3行:monitorenter指令尝试获取栈顶this对象的监视器锁,如果成功就继续往下执行,如果已经有其他线程持有,则进入等待状态
- 4-11行:执行++count指令
- 14-15行:能执行到这一步说明monitorenter已经获得this的锁,栈上没有元素了,所以调用 aload_1将局部变量表中的this对象入栈,调用monitorexit判断能否释放
- 19-23行:执行异常处理,我们的代码没有try-catch但是字节码层面自动给我们加上了。
JVM保证一个monitor一次只能被一个线程占有。monitorenter和monitorexit是两个与监视器相关的字节码指令。当线程执行monitorenter的时候尝试获取栈顶对象的监视器monitor,也就是尝试获取锁,如果此时monitor没有被其他线程占用就获得锁,monitor计数器设置为1,当线程已经获得monitor的所有权了,monitorenter指令也会顺利执行,monitor计数器+1.如果其他线程拥有monitor的所有权,当前线程会阻塞,直到monitor计数器变为0。
当线程执行monitorexit指令的时候,监视器计数器-1,计数器为0的时候锁被释放,其他等待的线程可以尝试获得monitor的所有权.
思考:为什么我们的代码没有try-catch但是字节码层面19-23行自动给我们加上了?
因为编译器必须保证无论同步代码块中的代码以何种方式结束,代码中每次调用monitorenter必须执行对应的monitorexit指令。如果执行了monitorenter没有执行 monitorexit指令,monitor一直被占用,则其他线程没有办法获得锁。如果执行monitorexit的线程原本并没有monitor的所有权,那么执行monitorexit指令时候会抛出illegalMonitorStateException异常。为了保证这一点,编译器自动加了一个异常处理器,这个异常处理器保证了方法正常退出和异常退出均能正常释放锁。可以理解为
public void _foo() throws Throwable{
monitorenter(lock);
try{
bar();
}finally{
monitorexit(lock);
}
}
根据之前介绍的try-catch-finally的字节码实现原理,finally语句块会被编译器复制到方法正常退出和异常退出的地方,语义上等价于一下代码
public void _foo() throws Throwable{
monitorenter(lock);
try{
bar();
monitorexit(lock);
}catch(Throwable e){
monitorexit(lock);
throw e;
}
}
同步方法
Java代码
public class SynchronizedDemo2 {
public synchronized void method() {
System.out.println("synchronized 方法");
}
}
底层源码
Synchronized同步方法的时候,在Java字节码层面,通过查看方法表的访问标志,查看是否存在ACC_SYNCHRONIZED标识,它的标志名是0x0020,如果有,就说明该方法是一个同步方法,然后隐式地调用monitorenter和monitorexit。
HotSpot层面
在JVM层面,使用Synchronized上锁就是在Markword中记录锁的信息,从而实现锁升级过程
public class Test_Sync{
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
//synchronized锁定这个对象
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
输出结果分别为对象o的markword信息
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 88 f2 95 02 (10001000 11110010 10010101 00000010) (43381384)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
对比后我们可以发现,对象o未被锁定的时候看到锁标志位为01(00000001),为无锁状态,而对象o被锁定的时候锁标志位为00,表示轻量级锁。
Monitorenter源码
我们进入InterpreterRuntime:: monitorenter方法
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
//如果使用偏向锁,就进入Fast_enter,避免不必要的锁膨胀,如果不是偏向锁,就进入slow_enter,也就是锁升级
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
"must be NULL or an object");
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
上面的重点是if、else判断,UseBiasedLocking判断是否使用偏向锁,如果使用,则进入ObjectSynchronizer::fast_enter,快速进入,避免不必要的锁膨胀,如果不是偏向锁,则调用ObjectSynchronizer::slow_enter,也兜底方案
偏向锁
fast_enter,偏向锁,在safepoint的时候撤销并且定偏向锁,如果成功就返回,不成功就slow_enter,也就是锁升级。
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) {
//如果使用偏向锁,那么尝试偏向
if (!SafepointSynchronize::is_at_safepoint()) {
//如果线程不在安全点,那么就尝试BiasedLocking::revoke_and_rebias实现撤销并且重偏向
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
//偏向成功,直接返回
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
return;
}
} else {
assert(!attempt_rebias, "can not rebias toward VM thread");
//进入这里说明线程在安全点并且撤销偏向
BiasedLocking::revoke_at_safepoint(obj);
}
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
//使用兜底方案,slow_enter
slow_enter (obj, lock, THREAD) ;
}
如果是偏向锁的话,第一个访问代码块的线程,把它的线程ID写到对象的Markword里面。
因为偏向锁是针对原始的不常发生线程争用时的方案,如果发送线程争用,那么会撤销偏向锁,然后调用slow_enter。
轻量级锁
而线程进入slow_enter,调用cmpxchg进行自旋(cmpxchg),如果成功则返回,说明获得轻量级锁;如果不成功,就进入锁膨胀
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对象中
lock->set_displaced_header(mark);
//通过cmpxchg进入自旋替换对象头为lock对象地址,如果替换成功则直接返回,表明获得了轻量级锁,不然继续自旋
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
return ;
}
// 否则判断当前对象是否上锁,并且当前线程是否是锁的占有者,如果是markword的指针指向栈帧中的LR,则重入
} 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;
}
#if 0
// The following optimization isn't particularly useful.
if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
lock->set_displaced_header (NULL) ;
return ;
}
#endif
// 代码执行到这里,说明有多个线程竞争轻量级锁,轻量级锁通过`inflate`进行膨胀升级为重量级锁
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
这里轻量级锁是通过BasicLock对象来实现的,在线程JVM栈中产生一个LR(lock Record)的栈桢,然后他们两个CAS竞争锁,成功的,就会在Markword中记录一个指针(62位),这个指针指向竞争成功的线程的LR,另外一个线程CAS自旋继续竞争,等到前面线程用完了,才进入。这就是自旋锁的由来。
重量级锁
inflate
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);则是进行锁膨胀,升级为重量级锁。主要分为两部,其中inflate用于获取监视器monitor,enter用于抢占锁
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
// Inflate mutates the heap ...
// Relaxing assertion for bug 6320749.
assert (Universe::verify_in_progress() ||
!SafepointSynchronize::is_at_safepoint(), "invariant") ;
for (;;) { //通过无意义的循环实现自旋操作
const markOop mark = object->mark() ;
assert (!mark->has_bias_pattern(), "invariant") ;
if (mark->has_monitor()) {//has_monitor是markOop.hpp中的方法,如果为true表示当前锁已经是重量级锁了
ObjectMonitor * inf = mark->monitor() ;//获得重量级锁的对象监视器直接返回
assert (inf->header()->is_neutral(), "invariant");
assert (inf->object() == object, "invariant") ;
assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
return inf ;
}
if (mark == markOopDesc::INFLATING()) {//膨胀等待,表示存在线程正在膨胀,通过continue进行下一轮的膨胀
TEVENT (Inflate: spin while INFLATING) ;
ReadStableMark(object) ;
continue ;
}
if (mark->has_locker()) {//表示当前锁为轻量级锁,以下是轻量级锁的膨胀逻辑
ObjectMonitor * m = omAlloc (Self) ;//获取一个可用的ObjectMonitor
// Optimistically prepare the objectmonitor - anticipate successful CAS
// We do this before the CAS in order to minimize the length of time
// in which INFLATING appears in the mark.
m->Recycle();
m->_Responsible = NULL ;
m->OwnerIsThread = 0 ;
m->_recursions = 0 ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class
/**将object->mark_addr()和mark比较,如果这两个值相等,则将object->mark_addr()
改成markOopDesc::INFLATING(),相等返回是mark,不相等返回的是object->mark_addr()**/
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
if (cmp != mark) {//CAS失败
omRelease (Self, m, true) ;//释放监视器
continue ; // 重试
}
markOop dmw = mark->displaced_mark_helper() ;
assert (dmw->is_neutral(), "invariant") ;
//CAS成功以后,设置ObjectMonitor相关属性
m->set_header(dmw) ;
m->set_owner(mark->locker());
m->set_object(object);
// TODO-FIXME: assert BasicLock->dhw != 0.
guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;
object->release_set_mark(markOopDesc::encode(m));
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite stacklock) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(void *) object, (intptr_t) object->mark(),
object->klass()->external_name());
}
}
return m ; //返回ObjectMonitor
}
//如果是无锁状态
assert (mark->is_neutral(), "invariant");
ObjectMonitor * m = omAlloc (Self) ; ////获取一个可用的ObjectMonitor
//设置ObjectMonitor相关属性
m->Recycle();
m->set_header(mark);
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
/**将object->mark_addr()和mark比较,如果这两个值相等,则将object->mark_addr()
改成markOopDesc::encode(m),相等返回是mark,不相等返回的是object->mark_addr()**/
if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
//CAS失败,说明出现了锁竞争,则释放监视器重行竞争锁
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.
}
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite neutral) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(void *) object, (intptr_t) object->mark(),
object->klass()->external_name());
}
}
return m ; //返回ObjectMonitor对象
}
}
该方法有四种情况
- mark->has_monitor() 说明已经膨胀为重量级锁,判断lock标志位是否为10,如果是则获得对象监视器
- 锁处于inflating,说明其他线程正在执行锁膨胀,那么当前线程通过自旋等待其他线程完成锁膨胀
- 如果当前线程已经是轻量级锁需要锁膨胀,那么通过omAlloc方法获得一个可用的ObjectMonitor,并设置初始数据;然后通过CAS将对象头设置为`markOopDesc:INFLATING,表示当前锁正在膨胀,如果CAS失败,继续自旋
- 如果是无锁状态,类似情况3
enter
void ATTR ObjectMonitor::enter(TRAPS) {
// The following code is ordered to check the most common cases first
// and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
Thread * const Self = THREAD ;
void * cur ;
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (cur == NULL) {//CAS成功
// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert OwnerIsThread == 1
return ;
}
if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions ++ ;
return ;
}
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
// Commute owner from a thread-specific on-stack BasicLockObject address to
// a full-fledged "Thread *".
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
// We've encountered genuine contention.
assert (Self->_Stalled == 0, "invariant") ;
Self->_Stalled = intptr_t(this) ;
// Try one round of spinning *before* enqueueing Self
// and before going through the awkward and expensive state
// transitions. The following spin is strictly optional ...
// Note that if we acquire the monitor from an initial spin
// we forgo posting JVMTI events and firing DTRACE probes.
if (Knob_SpinEarly && TrySpin (Self) > 0) {
assert (_owner == Self , "invariant") ;
assert (_recursions == 0 , "invariant") ;
assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
Self->_Stalled = 0 ;
return ;
}
assert (_owner != Self , "invariant") ;
assert (_succ != Self , "invariant") ;
assert (Self->is_Java_thread() , "invariant") ;
JavaThread * jt = (JavaThread *) Self ;
assert (!SafepointSynchronize::is_at_safepoint(), "invariant") ;
assert (jt->thread_state() != _thread_blocked , "invariant") ;
assert (this->object() != NULL , "invariant") ;
assert (_count >= 0, "invariant") ;
// Prevent deflation at STW-time. See deflate_idle_monitors() and is_busy().
// Ensure the object-monitor relationship remains stable while there's contention.
Atomic::inc_ptr(&_count);
EventJavaMonitorEnter event;
{ // Change java thread status to indicate blocked on monitor enter.
JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);
DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
if (JvmtiExport::should_post_monitor_contended_enter()) {
JvmtiExport::post_monitor_contended_enter(jt, this);
}
OSThreadContendState osts(Self->osthread());
ThreadBlockInVM tbivm(jt);
Self->set_current_pending_monitor(this);
// TODO-FIXME: change the following for(;;) loop to straight-line code.
for (;;) {
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition()
// or java_suspend_self()
EnterI (THREAD) ;
if (!ExitSuspendEquivalent(jt)) break ;
//
// We have acquired the contended monitor, but while we were
// waiting another thread suspended us. We don't want to enter
// the monitor while suspended because that would surprise the
// thread that suspended us.
//
_recursions = 0 ;
_succ = NULL ;
exit (false, Self) ;
jt->java_suspend_self();
}
Self->set_current_pending_monitor(NULL);
}
...//此处省略无数行代码
主要是通过CAS判断当前线程的指针和监视器的_owner比较替换,如果成功了直接返回,如果失败了就判断当前线程是不是占用了监视器,如果是,则是重入的,次数加1,再开始竞争,竞争的方式有自旋竞争(TrySpin)和等待竞争(EnterI)
Monitorexit源码
轻量级锁释放
首先释放轻量级锁,调用monitorexit
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
if (elem == NULL || h_obj()->is_unlocked()) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
// Free entry. This must be done here, since a pending exception might be installed on
// exit. If it is not cleared, the exception handling code will try to unlock the monitor again.
elem->set_obj(NULL);
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
将当前线程栈帧中锁记录空间中的Mark Word替换到锁对象的对象头中,如果成功表示锁释放成功。否则,锁膨胀成重量级锁,实现重量级锁的释放锁逻辑
重量级锁释放
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * Self = THREAD ;
if (THREAD != _owner) {//如果当前锁对象中的_owner没有指向当前线程
//如果_owner指向的BasicLock在当前线程栈上,那么将_owner指向当前线程
if (THREAD->is_lock_owned((address) _owner)) {
// Transmute _owner from a BasicLock pointer to a Thread address.
// We don't need to hold _mutex for this transition.
// Non-null to Non-null is safe as long as all readers can
// tolerate either flavor.
assert (_recursions == 0, "invariant") ;
_owner = THREAD ;
_recursions = 0 ;
OwnerIsThread = 1 ;
} else {
// NOTE: we need to handle unbalanced monitor enter/exit
// in native code by throwing an exception.
// TODO: Throw an IllegalMonitorStateException ?
TEVENT (Exit - Throw IMSX) ;
assert(false, "Non-balanced monitor enter/exit!");
if (false) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
return;
}
}
//如果当前,线程重入锁的次数,不为0,那么就重新走ObjectMonitor::exit,直到重入锁次数为0为止
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
TEVENT (Inflated exit - recursive) ;
return ;
}
...//此处省略很多代码
for (;;) {
if (Knob_ExitPolicy == 0) {
OrderAccess::release_store(&_owner, (void*)NULL); //释放锁
OrderAccess::storeload(); // See if we need to wake a successor
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
TEVENT(Inflated exit - simple egress);
return;
}
TEVENT(Inflated exit - complex egress);
//省略部分代码...
}
//省略部分代码...
ObjectWaiter * w = NULL;
int QMode = Knob_QMode;
//根据QMode的模式判断,
//如果QMode == 2则直接从_cxq挂起的线程中唤醒
if (QMode == 2 && _cxq != NULL) {
w = _cxq;
ExitEpilog(Self, w);
return;
}
//省略部分代码... 省略的代码为根据QMode的不同,不同的唤醒机制
}
}
重量级锁的释放是通过 ObjectMonitor::exit来实现的,释放以后会通知被阻塞的线程去竞争锁
- 判断当前锁对象中的owner没有指向当前线程,如果owner指向的BasicLock在当前线程栈上,那么将_owner指向当前线程
- 如果当前锁对象中的_owner指向当前线程,则判断当前线程重入锁的次数,如果不为0,继续执行ObjectMonitor::exit(),直到重入锁次数为0为止
- 释放当前锁,并根据QMode的模式判断,是否将_cxq中挂起的线程唤醒。还是其他操作
总结
字节码层面,synchronized的原理可以理解为在字节码中加了monitorenter和monitorexit来实现的;而深入HotSpot源码的monitorenter源码部分,可以看出synchronized底层原理其实涉及到偏向锁->轻量级锁->重量级锁的锁膨胀过程。
多数时间,被synchronized修饰的代码块只有一个线程在执行,根本没有多线程竞争,这种情况没必要设置竞争机制,所以产生了偏向锁,他在monitorenter中是通过fast_enter来实现的,实际上就是第一个访问代码块的线程,把它的线程ID写到MarkWord中去。
一旦有线程来竞争了,fast_enter就撤销偏向锁,然后调用slow_enter,竞争的线程通过CAS自旋竞争,成功的线程就会在Markword中记录一个指针,指向竞争成功的线程的LR,而另一个线程CAS自旋继续竞争,等前面线程用完了才进入。
由于自旋是消耗CPU资源的,并且是无意义的,如果锁的时间过长或者自旋线程多,CPU资源会被大量消耗,而重量级锁有等待队列,拿不到锁的线程会进入等待队列,不需要消耗CPU资源(采用CFS完全公平策略,根据时间片比例分配)
所以如果线程自旋超过10次,或者自旋线程数超过CPU核数的一半,此时会升级为重量级锁,也就是调用inflate用于获取监视器monitor,enter用于抢占锁
参考
HotSpot源码
《深入理解java虚拟机》
《Java并发实现原理》