1 synchronized指令实现
通过前面一篇文章我们可知道 synchronized有两种使用方式一种是在方法上,一中是在代码块使用。在方法上我们通过分析字节码发现是在方法标志上设置ACC_SYNCHRONIZED标志代码块上的实现是通过 monitorenter指令和monitorexit的指令来实现了一段代码缓冲区。
2 synchronized过程解析
2.1 ACC_SYNCHRONIZED过程解析
bytecodeInterpreter 是jvm 执行过程中的编译器,主要对字节码进行解释执行。在解释执行方法的过程发现如果发下方法有ACC_SYNCHRONIZED会先进行拿锁,拿到锁的之后才会执行。具体流程如下:
从上图可以清晰看出当是ACC_SYNCHRONIZED方法的时候会等到拿到锁的过程才开始执行。下面是源码信息:
case method_entry: {
THREAD->set_do_not_unlock();
//在这中间去掉了一些无关代码
//synchronized 整个synchronized方法实现的过程
if (METHOD->is_synchronized()) {
oop rcvr;
//如果方法是static的 要获取到类操作对象
if (METHOD->is_static()) {
rcvr = METHOD->constants()->pool_holder()->java_mirror();
} else {
//获取到当前实例的操作对象
rcvr = LOCALS_OBJECT(0);
VERIFY_OOP(rcvr);
}
// 线程栈的存储监控对象的数组
BasicObjectLock* mon = &istate->monitor_base()[-1];
//获取到对应操作对象
oop monobj = mon->obj();
//判断是否启动偏向锁
bool success = UseBiasedLocking;
//如果启用偏向锁 现在的jdk里面默认是不启用的这段代码可以直接跳过
if (UseBiasedLocking) {
markOop mark = rcvr->mark();
//主要用来判断是否已经拿了偏向锁,如果拿了是否是当前线程
if (mark->has_bias_pattern()) {
//如果不是当前线程的就直接失败
} else {
cas_label:
success = false;
}
}
//如果没有启动偏向锁 或者偏向锁失败
if (!success) {
//设置锁对象
markOop displaced = rcvr->mark()->set_unlocked();
mon->lock()->set_displaced_header(displaced);
//尝试进行获取锁对象 ,如果成功就不执行里面内容
if (Atomic::cmpxchg_ptr(mon, rcvr->mark_addr(), displaced) != displaced) {
// 如果已经拿到锁了是重入的情况
if (THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
mon->lock()->set_displaced_header(NULL);
} else {
//调用标准的monitorenter 进入锁竞争机制
CALL_VM(InterpreterRuntime::monitorenter(THREAD, mon), handle_exception);
}
}
}
}
THREAD->clr_do_not_unlock();
//执行代码
goto run;
}
通过上图中代码,我们最终可以清晰看到是通过InterpreterRuntime::monitorenter(THREAD, mon)来实现获取锁的调用的。
2.2 monitorenter 指令解析过程
bytecodeInterpreter当判断代码是_monitorenter指令进行编译,加入整个获取锁的逻辑。
核心的关键点:
1 从栈的底部挨个寻找空闲的锁的对象指针,获取到空闲的对象绑定锁。这就是简单的对象池技术和数据库连接池类似,重复利用空闲的空间。
2 尝试加入偏向锁,如果失败了就启动InterpreterRuntime::monitorenter 来通过锁晋升的机制拿锁。
JVM的bytecodeInterpreter代码如下:
CASE(_monitorenter): {
//获取synchronized 同步的对象
oop lockee = STACK_OBJECT(-1);
//判断对象是否为空 为空就无法加锁了
CHECK_NULL(lockee);
//获取到锁结束的位置
BasicObjectLock* limit = istate->monitor_base();
//获取到栈开始位置
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
BasicObjectLock* entry = NULL;
//遍历已经获得的锁 判断是否有当前_monitorenter 要拿到的锁
while (most_recent != limit ) {
if (most_recent->obj() == NULL){
entry = most_recent;
}
else if (most_recent->obj() == lockee) {
break;
}
most_recent++;
}
//如果前面没有拿到过锁
if (entry != NULL) {
entry->set_obj(lockee);
markOop displaced = lockee->mark()->set_unlocked();
entry->lock()->set_displaced_header(displaced);
//尝试加偏向锁
if (Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
if (THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
entry->lock()->set_displaced_header(NULL);
} else {
//加锁失败获取重量级锁
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
}
}
2.3 monitorexit 指令解析过程
bytecodeInterpreter判断代码中有monitorexit指令处理流程就是比较简单了。总过只完成两件事,一个是释放本地线程栈中找到要释放的对象,二调用InterpreterRuntime::monitorexit将锁对象释放掉就可以了。整个核心代码就如下:
CASE(_monitorexit): {
oop lockee = STACK_OBJECT(-1);
CHECK_NULL(lockee);
// derefing's lockee ought to provoke implicit null check
// find our monitor slot
BasicObjectLock* limit = istate->monitor_base();
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
//从线程栈的锁中找到当前要释放的锁对象
while (most_recent != limit ) {
if ((most_recent)->obj() == lockee) {
BasicLock* lock = most_recent->lock();
markOop header = lock->displaced_header();
most_recent->set_obj(NULL);
// If it isn't recursive we either must swap old header or call the runtime
if (header != NULL) {
if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
// restore object for the slow case
most_recent->set_obj(lockee);
//将锁对线程锁象释放掉
CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
}
}
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
}
most_recent++;
}
// Need to throw illegal monitor state exception
CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);
ShouldNotReachHere();
}
3 锁获取实现逻辑
通过前面的内容我们知道不论是ACC_SYNCHRONIZED方法标志还是_monitorenter指令最终都是nterpreterRuntime类monitorenter方法来获取锁的。
3.1 monitorenter方法
InterpreterRuntime的monitorenter方法逻辑也非常简单,就是判断是否启动偏向锁,如果启动偏向锁就调用ObjectSynchronizer类fast_enter方法来获取偏向锁,如果不启动就调用slow_enter获取轻量级锁。
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
Handle h_obj(thread, elem->obj());
//判断是否开启偏向锁 ,如果开启fast_enter获取偏向锁
if (UseBiasedLocking) {
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
//如果开启偏向锁调用slow_enter获取偏轻量级锁
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
如果fast_enter调用失败后就也会调用slow_enter来获取轻量级锁,如果slow_enter失败化就会调用inflate锁膨胀的机制来获取重量级锁的。整个锁的获取流程是fast_enter(偏向锁) ->slow_enter(轻量级锁)->inflate(重量级锁)来逐步升级获取锁机制的。
3.2 fast_enter方法
fast_enter 从字面上看就是快速获取到锁的方法,核心的实现是调BiasedLocking类revoke_and_rebias方法加偏向锁。本质是就是修改锁对象的mark word的值,把线程ID写入到锁对象的mark word值中。
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) {
//不在安全点
if (!SafepointSynchronize::is_at_safepoint()) {
//删除以前的偏向锁标志 并重新加上偏向锁标志
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 (obj, lock, THREAD) ;
}
如果获取失败就会调用slow_enter 来获取轻量级锁。
3.3 slow_enter方法
slow_enter就是来实现获取轻量级锁,本质就是lock的内存地址赋值给锁对象。设置成功了之后就自动就自动返回了。核心的代码是:
mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)
通过上面代清晰看到通过一次cas的操作来进行了地址赋值。
整个slow_enter整体代码如下:
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
//第一步 获取锁对象的“mark word"
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
//第二步,判断当前锁是否是无锁状态 后两位标志位为01
if (mark->is_neutral()) {
// 锁中的Mark值和对象中的mark 值相同
lock->set_displaced_header(mark);
//通过CAS尝试将锁对象Mark Word更新为指向lock Record对象的指针,
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
return ;
}
}
//第四步,如果当前mark处于加锁状态,且线程帧栈中的owner指向当前锁,则执行同步代码,
else if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
//清除刚才设置的值
lock->set_displaced_header(NULL);
//获取成功了直接返回
return;
}
//设置成初始化状态
lock->set_displaced_header(markOopDesc::unused_mark());
//进入锁膨胀的机制 先构造锁对象 然后加入锁对象等待队列
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
设计的巧妙点
前面在读 Mark Word的时候一直有个疑问为什么轻量级锁的前两位是00开头的?
通过上面的代码我们可以清晰的知道 lock地址最大范围是不能大不可能到达62位的,所以高位一定是00开头的。这样我们就巧妙通过了赋值直一次接修改了Mark Word的值。
3.4 inflate方法
inflate方法本质上就是获取到对象监视器ObjectMonitor然后当前线程加入到等待期里去等待。在多线程线程同时获取ObjectMonitor监视器的时候可能遇到如下几种情况:
1 其他线程已经获取到ObjectMonitor。
2 其他线程正在同时掉 inflate方法来获取ObjectMonitor过程中。
3 其他线程加了轻量级锁。
4 其他线程已经获取过锁并且释放掉了。
其实应对的策略也是非常简单靠谱的:
1 其他线程已经获取到ObjectMonitor,就直接返回其他线构建好的ObjectMonitor。
2 其他线程同时掉 inflate方法来获取ObjectMonitor过程中,情况不明先观望,后发制人。
3 其他线程加了轻量级锁,帮他深一级构建重量级锁ObjectMonitor。
4 其他线程已经获取过锁并且释放掉了,就构建一个新的ObjectMonitor。
核心的代码逻辑如下:
1.如果当前锁已经为重量级锁了,直接返回锁的monitor对象。
2.如果正在膨胀的过程中,在完成膨胀过程中,其他线程必须等待。
3.如果当前为轻量级锁,构造新的ObjectMonitor,使其膨胀为重量级锁。
4如果象是空闲的时候,重新构建一个ObjectMonitor,并将当前线程设置为owner。
代码细节如下:
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
//开始自旋转操作
for (;;) {
const markOop mark = object->mark() ;
//1.如果当前锁已经为重量级锁了,直接返回
if (mark->has_monitor()) {
ObjectMonitor * inf = mark->monitor() ;
return inf ;
}
//2.如果正在膨胀的过程中,在完成膨胀过程中,其他线程必须等待
if (mark == markOopDesc::INFLATING()) {
ReadStableMark(object) ;
continue ;
}
//3.如果当前为轻量级锁,迫使其膨胀为重量级锁
if (mark->has_locker()) {
//构造一个monitor对象
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
//先加个膨胀标志,紧紧是个标志 因为从轻量级到重量级需要一个复杂的过程
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
if (cmp != mark) {
omRelease (Self, m, true) ;
continue ; // Interference -- just retry
}
//获取到当前的lock 标志
markOop dmw = mark->displaced_mark_helper() ;
//将他放入到moitor 头节点中来
m->set_header(dmw) ;
//设置锁的owner
m->set_owner(mark->locker());
//设置锁的对象
m->set_object(object);
//讲新的monitor对象就设置到对象头中
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 * m = omAlloc (Self) ;
// prepare m for installation - set monitor to initial state
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
//讲monitor 防止到对象的mark word中
if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
m->set_object (NULL) ;
m->set_owner (NULL) ;
m->OwnerIsThread = 0 ;
m->Recycle() ;
omRelease (Self, m, true) ;
m = NULL ;
continue ;
}
return m ;
}
}
4 锁释放实现逻辑
同时ACC_SYNCHRONIZED 方法标志与monitorexit指令都是通过nterpreterRuntime类的monitorexit方法来释放锁的实现。
4.1 monitorexit方法
从下图代码中可以清晰的看到interpreterRuntime的monitorexit方法主要是通过ObjectSynchronizer类的slow_exit来释放获取锁对象的。
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());
if (elem == NULL || h_obj()->is_unlocked()) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
//释放对象
ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
elem->set_obj(NULL);
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
4.2 slow_exit方法
slow_exit本是个封装方法,里面实现的逻辑是通过fast_exit来实现的。
void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
fast_exit (object, lock, THREAD) ;
}
4.2 fast_exit方法
通过前面加锁的过程我们可以清晰知道,偏向锁与轻量级锁只会修改锁对象的mark word的值,而重量级锁我们会整个构造一个锁监视对象ObjectMonitor,将线程加入到等待队列中。其实退出操作也刚好是逆向操作。如果是偏向锁就与轻量级锁就去掉mark word的值,如果重量级锁拿到ObjectMonitor监视器,然后将其从队列中除去。详细代码如下:
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
// 获取到锁对象的mark word
markOop dhw = lock->displaced_header();
markOop mark ;
//如果线程堆栈中的Displaced Mark Word为null
if (dhw == NULL) {
//获取到对象的mark word
mark = object->mark() ;
//从monitor 中删除
if (mark->has_monitor()) {
ObjectMonitor * m = mark->monitor() ;
}
return ;
}
//获取到具体的mark值
mark = object->mark() ;
//锁中的mark和对象中mak相同 直接删除
if (mark == (markOop) lock) {
if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
TEVENT (fast_exit: release stacklock) ;
return;
}
}
//获取到锁监视器对象
ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}