在偏向锁出现竞争后,加锁失败的线程会把Mark Word中的锁状态改为轻量级锁,这样其他线程再来时就会走向轻量级锁的加锁流程。下面开始轻量级锁获取流程分析,代码在bytecodeInterpreter.cpp#1816。
CASE(_monitorenter): {
oop lockee = STACK_OBJECT(-1);
...
if (entry != NULL) {
...
// 上面省略的代码中如果CAS操作失败也会调用到InterpreterRuntime::monitorenter
// traditional lightweight locking
if (!success) {
// 构建一个无锁状态的Displaced Mark Word
markOop displaced = lockee->mark()->set_unlocked();
// 设置到Lock Record中去
entry->lock()->set_displaced_header(displaced);
bool call_vm = UseHeavyMonitors;
if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
// 如果CAS替换不成功,代表锁对象不是无锁状态,这时候判断下是不是锁重入
// Is it simple recursive case?
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
entry->lock()->set_displaced_header(NULL);
} else {
// CAS操作失败则调用monitorenter
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
}
}
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
} else {
istate->set_msg(more_monitors);
UPDATE_PC_AND_RETURN(0); // Re-execute
}
}
如果锁对象不是偏向模式或已经偏向其他线程,则success
为false
。这时候会构建一个无锁状态的mark word
设置到Lock Record
中去,我们称Lock Record
中存储对象mark word
的字段叫Displaced Mark Word
。需要注意的是这个Displaced Mark Word
不是一个指针,而是一个副本。你或许很奇怪,每次加锁的时候都会创建Lock Record
,那么Lock Record
在线程中到底是个什么样的数据结构呢,这里我把加锁的过程画了出来,连同重量级锁加锁过程一起画了,因为他们是一个动态转换的过程,分开的话隔远了看着累。
轻量级锁 -> 重量级锁
Synchronized锁升级过程为:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。
假设线程进入轻量级锁。当线程执行到同步方法或方法中的同步代码块时,线程首先会创建锁记录对象(Lock Record),每个线程的有
synchronized
关键字的栈帧(每个方法就是一个栈帧)都会包含锁记录结构,内部可以存储锁定对象的mark work当线程出现竞争时,让锁记录中Object Reference指向锁对象,并尝试cas替换锁对象的Mark Work,将Mark Word的值存入Lock Record的地址,注意此时锁对象头中的锁状态是 00 ,表示线程获得轻量级锁,而
Lock Record
中的对象头副本是 01。这是因为Lock Record
中存储的是修改之前的对象头副本synchronized
是可重入的,当下次加锁线程还是当前线程时,轻量级锁会创建一个新的Lock Record
作为重入的标识,object reference依旧指向object对象,但此时的锁记录副本为 null当退出同步代码块时,如果锁记录为null,直接出栈,表示重入计数减一
当退出同步代码块时,锁记录不为null,这时使用cas将Lock Record中的Mark Word的副本值恢复给对象头,如果修改成功,则解锁成功。如果失败,说明有其它线程要进入同步代码块,锁已经发生锁膨胀,进入重量级解锁流程
如果有线程(Thread-A)已经占有了轻量级锁,另一个线程(Thread-B)再次进入同步代码块,检测到对象的mark work的标识为00,则会发生锁膨胀,将锁转变为重量级锁。为object申请Monitor锁,并让mark word指向Monitor,自己进入Monitor对象的entryList进行等待,此时Ower指向锁记录对象的对象引用
当Thread-A执行完同步代码块的代码,使用cas将Mark word的值恢复给对象头,此时会失败。这时为进入重量级解锁流程,按照Monitor地址找到Monitor对象,将Owner设置为null,并唤醒EntryList中等待的线程。