Java并发之Synchronized源码分析
Java并发之Synchronized源码分析
理论基础
主要通过参考以下文章深入了解源码:
- 通过分析ByteCodeInterpreter.cpp(在hotspot/src/share/vm/interpreter中)来了解:
死磕Synchronized底层实现–概论
死磕Synchronized底层实现–偏向锁
死磕Synchronized底层实现–轻量级锁
死磕Synchronized底层实现–轻量级锁 - 通过分析TemplateInterpreter(JVM当前使用的解释器)来了解:
JVM同步方法之偏向锁
首先,我们先清楚Synchronized的入口是哪里,从上面文章能了解到的Synchronized同步代码块内部实现的入口有两个,一个是ByteCodeInterpreter.cpp#1816,一个是templateTable_x86_64.cpp#3667。而我自己看源码发现Synchronized同步方法则是由ByteCodeInterpreter.cpp#682,由于模板解释器几乎都是使用汇编语言写的现阶段看不懂,也不想去看,所以还是直接了解ByteBodeInterpreter就足够了解原理了。
好了,此时我们进入正题,下面会通过贴源码然后逐个分析,最后再进行总结过程。首先分析Synchronized同步代码块的实现。(源码基于jdk1.8)
##Synchronized同步代码块
我们知道,一个同步块的进入会先获取锁,而通过“是否存在锁竞争”进行锁膨胀,而整个过程就是:无锁->偏向锁->轻量级锁->重量级锁。然后就可以通过源码去了解整个过程会发生什么和不同情况下会进行什么操作。
同步代码块是通过synchronized(
){}来指定的,然后通过编译的Class的反编译可以看到包含这代码块的方法Code属性中多了两种指令,分别为monitorenter和monitorexit,这里就不放例子了,需要的可以自行去尝试。然后就直接看回bytecodeInterpreter.cpp里面的具体实现吧。
monitorenter指令解析
获取偏向锁
这里先贴出整个monitor指令的解析,后面会对重要的点进行逐个分析(注意:下面是对于32位markword结构进行分析的)
//interpreter/bytecodeInterpreter.cpp#1911
//同步代码块的入口,需要先获取锁
CASE(_monitorenter): {
//这里先拿到锁对象,即synchronized($锁对象$)中的锁对象
oop lockee = STACK_OBJECT(-1);
// derefing's lockee ought to provoke implicit null check
CHECK_NULL(lockee);
//1.这里通过指针移位,找到一个指向空闲lock record(BasicObjectLock)的指 //针,这个空闲的lock record是用来存放锁对象和锁对象的markword的;如果找到 //与当前锁对象相同的lock record则认为是递归进入同步块(锁重入),重新分配 //一个指向空闲lock record的指针给entry。
BasicObjectLock* limit = istate->monitor_base();
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
BasicObjectLock* entry = NULL;
while (most_recent != limit ) {
if (most_recent->obj() == NULL) entry = most_recent;
else if (most_recent->obj() == lockee) break;
most_recent++;
}
//找到一个空闲的lock record,开始进入获取偏向锁流程
if (entry != NULL) {
entry->set_obj(lockee); //保存锁对象
int success = false; //用于判断是否使用偏向锁成功
//2.epoch_mask_in_place="...001100...",其中11就是存放epoch的位
//置,这个字段用来与epoch进行运算,如需了解可以看一下markOop.hpp
uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;
//取出对象头信息
markOop mark = lockee->mark();
//取hash位全为0
intptr_t hash = (intptr_t) markOopDesc::no_hash;
//3.尝试使用偏向锁,通过是否可偏向“1”和锁标记是否为无锁“01”,
if (mark->has_bias_pattern()) {
//锁对象的markword偏向锁和锁标记为“101”,表示满足偏向模式
uintptr_t thread_ident; //用于保存线程id
uintptr_t anticipated_bias_locking_value; //预期的偏向Markword值
thread_ident = (uintptr_t)istate->thread(); //当前线程id
//4.首先通过拿到klass的prototype_header(这里的markword可以理
//解为klass保存的markword值)("00...00[hash:25]****[age:4]101",
//无锁无hash的可偏向的markword),和当前线程进行‘或’运算得到新的
//markword("$线程id[23]$**[epoch:2]****[age:4]101"),再和锁对象
//的markword进行‘异或’运算,得到剩余不同的位,此时markword有四种可
//能,对于后面的每种情况。(后面进行‘与’运算是为了忽略age,把4位的age都
//变为0),主要通过比较线程id、epoch、偏向锁标记和锁标记
anticipated_bias_locking_value =
(((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
~((uintptr_t) markOopDesc::age_mask_in_place);
//分4种情况
if (anticipated_bias_locking_value == 0) {
//5.得到的预期值为0,则表示当前锁对象已经偏向当前线程,且epoch相同,
//说明与上一次进入同步块的线程相同,此锁对象还没有经过撤销偏向操作,
//就设置成功获取偏向锁,到此就完成了monitorenter指令的操作。
if (PrintBiasedLockingStatistics) {
(* BiasedLocking::biased_lock_entry_count_addr())++;
}
success = true;
}
else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
//6.此时得到的值为“00...[23]00[epoch]0000[age]100”,又因为之前
//已经判断锁对象的markword的偏向锁标记为1,说明是锁对象的klass已经
//关闭偏向锁(经过了bulk revoke操作,klass的prototype_header已
//经设置为“00...[29]001”),所以此时设置完markword后会直接走轻量
//级锁的逻辑
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
//尝试使用CAS将锁对象的markword替换为指向Class的初始对象头
//(header)的指针,如果CAS成功,此时的header的markword为
//“00...[23]**epoch****[age]001”
if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(*BiasedLocking::revoked_lock_entry_count_addr())++;
}
}
else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
//7.此时得到的值为“00...[23]**[epoch]0000[age]000”,说明了这个
//类对象的klass之前进行过批量重偏向,并设置当前的线程id
//得到新的markword,锁对象的klcass记录的epoch信息也在
//prototype_header中,这里相当于把锁对象的epoch值更新
markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
if (hash != markOopDesc::no_hash) {
new_header = new_header->copy_set_hash(hash);
}
//尝试使用CAS将锁对象的markword替换为指向klass的
//prototype_header(header)的指针,此时的header的markword
//为“$当前线程id[23]$**[epoch]0000[age]101”
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::rebiased_lock_entry_count_addr())++;
}
else {
//CAS设置失败,说明多线程在竞争锁,继续执行
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
else {
//8.尝试去偏向当前线程,以防锁对象是匿名偏向的
//下面使用‘与’运算取出锁对象除了线程id的其他位
markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |
(uintptr_t)markOopDesc::age_mask_in_place |
epoch_mask_in_place));
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
//添加当前线程id到markword中
markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
// debugging hint
DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
//尝试使用CAS将锁对象的markword替换为指向new_header的指针,
//此时的new_header的markword为
//“$当前线程id[23]$**[epoch]****[age]101”
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
}
else {
//CAS设置失败,说明多线程在竞争锁,继续执行
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
}
//获取偏向锁失败
if (!success) {
//9.拿到锁对象的markword并与“00...[29]001”进行‘或’运算,得到一个无
//锁状态锁对象的markword,并将lock record的_lock的
//displaced_header指向它,如果下面CAS成功,说明锁已膨胀为轻量级锁。
markOop displaced = lockee->mark()->set_unlocked();
entry->lock()->set_displaced_header(displaced);
//jvm参数,设置+UseHeavyMonitors会直接略过轻量级锁直接膨胀为重量级锁
bool call_vm = UseHeavyMonitors;
//尝试使用CAS将锁对象的markword替换为指向lock record(entry)的指针
if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
//如果没有设置过UseHeavyMonitors,则表示CAS失败,多线程在竞争锁
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
//10.如果没设置UseHeavyMonitors且锁重入了,就不浪费当前的栈空
//间,不分配markword只记录锁对象
entry->lock()->set_displaced_header(NULL);
} else {
//调用另一个方法,此时已经获取偏向锁失败了,且多线程在竞争,可能会
//去执行撤销偏向锁,膨胀为轻量级,也可能进行批量重偏向或批量撤销,
//还可能进行重偏向(视情况而定)
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
}
}
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
} else {
//此时是因为monitor record已经满了,需要重新执行
istate->set_msg(more_monitors);
UPDATE_PC_AND_RETURN(0); // 重新执行
}
}
上面已经把各个点的作用进行了简单的描述,下面就补充一下上面序号的内容。
- 主要是通过从当前线程的栈中找到空闲的lock record(BasicObjectLock),并将entry指向到空闲的lock record中;如果查找中发现锁对象已经存在lock record里,则会认为是递归进入同步代码块,也会为锁对象存在找到的新的空闲lock record中,关于具体细节,后面10点会讲到。现在继续看一下BaisadObjectLock的结构是怎么样的:
// runtime/basicLock.hpp#57
// class BasicObjectLock:
BasicLock _lock; //保存锁对象被替换的markword
oop _obj; //持有偏向锁的对象
// class BasicLock:
volatile markOop _displaced_header; //被替换的markword
- 可以看到,一个BasicObjectLock是当前线程用来存成功获取偏向锁的锁对象和锁对象被替换的markword的。通过if (most_recent->obj() == NULL) entry = most_recent;可以知道判断lock record的依据是其obj字段 是否为null。
通过按内存地址从低往高找到最后一个可用的lock record,找到的是内存地址最高的可用lock record。
- 关于类似字段xxx_mask_in_place都存在oops/markOop.hpp#130中,都是用来取出markword中对应的某个信息,例如lock_mask_in_place=00…[30]11用于取出锁标记字段。
- has_bias_pattern()用于判断是否属于偏向模式,具体实现如下:
// oops/markOop.hpp#181
//判断当前的markword的后三个位是否与偏向模式“101”匹配
bool has_bias_pattern() const {
//biased_lock_pattern = 5,即“101”
return (mask_bits(value(), biased_lock_mask_in_place) == biased_lock_pattern);
}
- 上面的注释把anticipated_bias_locking_value字段含义解释的很清楚了,主要看一下klass的prototype_header()方法存着的标准markword是用来干什么的。
通过源码可知它是在oops/klass.hpp中的,而klass信息中保存这_prototype_header这个字段,通过set_prototype_header来设置标准markword值,而引用主要有这几个:
而主要看下面4种引用,第一种是当一个Class文件在初始化klass信息时调用的,这里初始化_prototype_header=00…[30]01,而第三种是设置为偏向模式,即_prototype_header=00…[29]]101,剩下的两种是在批量重偏向和批量撤销用到的,后面在进行讲解。 - anticipated_bias_locking_value的第一种情况,这种情况最好理解,当前线程id之前已经获取过偏向锁,进行偏向锁重新获取,由于释放偏向锁中并没有修改锁对象的markword字段,只是通过把lock record的_obj=null,就表示释放偏向锁,所以这里重新获取并不需要进行什么逻辑,直接进入同步代码块。
- anticipated_bias_locking_value的第二种情况,此时表示偏向锁标识字段与klass中的prototype_header不同,说明已经经过了bulk revoke操作,表示当前锁对象的类不适合使用偏向锁,后面此类的所有的对象都会直接进入轻量级锁逻辑,跳转到下面的第9,
- anticipated_bias_locking_value的第三种情况,此时表示锁对象的epoch与klass中_prototype_header的epoch字段不同,说明当前锁对象属于进行bulk rebias后的第一个进来的当前类的对象,尝试去进行重偏向,直接把当前线程id和klass中的_prototype_header合并后通过CAS更新锁对象的markword,CAS成功说明已获得偏向锁,并进入同步块,失败则说明此时多线程在竞争,并走InterpreterRuntime::monitorenter的逻辑。
这里补充一个点,CALL_VM()方法的作用只是用于检查调用方法的搁置的异常。
- anticipated_bias_locking_value的其余情况,如果不属于上面三种情况,就会进行如下的操作,尝试去偏向当前线程,防止偏向模式属于匿名偏向(markword=‘00…[23][epoch]***[age]101’),就把当前线程id和klass的_prototype_header的信息合并,并通过CAS设置到锁对象的markword中,成功就继续执行同步块,失败照样走InterpreterRuntime::monitorenter的逻辑。
- 这一步是上面的获取偏向锁失败进入的逻辑,能执行到的只能是进入到了上面的第二种情况,此时可以说已经做好膨胀为轻量级的准备了,把原始的markword值设置到lock record中保存,并把锁对象的markword前30位全都用来存放指向lock record的指针首地址。并通过CAS设置到锁对象的markword中,成功说明获取轻量级锁成功,失败则走InterpreterRuntime::monitorenter的逻辑。
- 进入到这一步,说明此时是锁重入,锁重入的例子:
synchronized(obj){
synchronized(obj){
}
}
- 此时由于相当于递归进入同步块,需要记录栈的长度,以便后续的锁释放,Hotspot中使用了把lock record重复记录锁对象信息,并为了节约栈空间,就不重复设置displaced_header,上面的代码的轻量级锁结构如下:
撤销偏向锁
可以知道,上面的获取偏向锁失败或轻量级锁CAS失败(说明存在多线程竞争)或者设置了+UseHeavyMonitors才会进入InterpreterRuntime::monitorenter函数中,那么现在就继续分析。
补充:撤销和释放的区别:撤销是指在获取偏向锁的过程因为不满足条件导致要将锁对象改为非偏向锁状态;释放是指退出同步块时的过程(指令moniterexit指令过程)
//interpreter/interpreterRuntime.cpp#602
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
...
if (UseBiasedLocking) {
//进行偏向锁的流程
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
//进入轻量级锁的流程
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
...
首先我们先看有关于偏向锁是如何撤销、什么情况下会进行批量重偏向和批量撤销操作。
//runtime/synchronizer.cpp#168
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
//判断启动参数是否设置了UseBiasedLocking
if (UseBiasedLocking) {
//判断是否到达了safepoint,撤销操作需要等到全局安全点才能进行
if (!SafepointSynchronize::is_at_safepoint()) {
//当前线程是Java线程,revoke_and_rebias是偏向锁的主要实现,用于撤销或重偏向
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
//此时经过了撤销和重偏向则说明又获得了偏向锁就直接退出。
return;
}
} else {
//当前线程是VM线程,则可以进行偏向锁撤销
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) ;
}
通过上面的注释应该都能理解的差不多,由于撤销操作需要经过safepoint才能进行撤销,所以下面的BiasedLocking::revoke_at_safepoint()是由VM线程去执行的。
这里继续了解BiasedLocking::revoke_and_rebias的主要实现:
//runtime/biasedLocking.cpp#529
BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
...
markOop mark = obj->mark(); //拿到锁对象的markword
//1.判断markword是否属于匿名偏向(没有记录线程id)且允不允许尝试重偏向,
//这个参数是由上面传进来的,是true
if (mark->is_biased_anonymously() && !attempt_rebias) {
//2.锁对象是匿名偏向的而且attempt_rebias=false,synchronized的
//实现不会调用这段这段代码,但是锁对象的hashcode方法被调用时会进来尝试
//使用CAS去撤销偏向,而不用经过safepoint,HotSpot不保证一定能成功,
//如果此时有多线程在竞争则无法直接替换
markOop biased_value = mark;
markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
//3.撤销偏向成功,返回标识BIAS_REVOKED
if (res_mark == biased_value) {
return BIAS_REVOKED;
}
} else if (mark->has_bias_pattern()) {
// markword满足偏向模式
Klass* k = obj->klass();
markOop prototype_header = k->prototype_header();
if (!prototype_header->has_bias_pattern()) {
//但klass的_prototype_header不满足偏向模式,说明此时klass对应的对象已经经
//过bulk revote操作了,而bulk revote会使klass的prototype_header设置
//为“001”。尝试使用CAS去设置锁对象的markword指向klass的
//prototype_header,如果成功,返回标识BIAS_REVOKED
markOop biased_value = mark;
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);
...
return BIAS_REVOKED;
} else if (prototype_header->bias_epoch() != mark->bias_epoch()) {
//markword中的epoch过期,说明前面进行过一次bulk rebias操作,需要进行更新
if (attempt_rebias) {
//Synchronized会走这段逻辑,会尝试去进行重偏向操作,把所偏向当前线程。
assert(THREAD->is_Java_thread(), "");
markOop biased_value = mark;
//这里会把线程id、年龄、epoch和“101”合并成一个markword
markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) {
//CAS成功,说明进行了重偏向
return BIAS_REVOKED_AND_REBIASED;
}
} else {
//如果不允许重偏向,则尝试使用CAS去设置锁对象的markword指向klass的
//prototype_header,如果成功,返回标识BIAS_REVOKED
markOop biased_value = mark;
markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) {
return BIAS_REVOKED;
}
}
}
}
//4.这里很重要,首先这个是用来更新撤销操作计数器的值,并在里面判断是否需要进行bulk
//rebias或bulk revoke,后面会具体进去分析,读者可以先去下面的第4点了解这个方法的
//实现。这里会返回4种结果分别为:
//HR_NOT_BIASED,HR_SINGLE_REVOKE,HR_BULK_REBIAS,HR_BULK_REVOKE
HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);
if (heuristics == HR_NOT_BIASED) {
//不偏向,后面就会直接锁膨胀
return NOT_BIASED;
} else if (heuristics == HR_SINGLE_REVOKE) {
//当前锁对象的偏向锁撤销
Klass *k = obj->klass();
markOop prototype_header = k->prototype_header();
//判断markwork是否已经偏向当前线程和klass保存的epoch和锁对象的是否一致
if (mark->biased_locker() == THREAD &&
prototype_header->bias_epoch() == mark->bias_epoch()) {
//markword偏向当前线程且epoch一致,则会进行对自己线程的偏向撤销,例如已经计
//算hashcode的markword会去对自己进行偏向锁撤销,而且不需要经过safepoint,
//这样就无需通知其他线程等待全局安全点,减少消耗。
//这下面的操作都所以在同一线程的操作,所以只是操作自己的线程栈,无需进行等到 //safepoint就可以撤销偏向锁
ResourceMark rm;
//5.这里去执行撤销偏向锁,这里的撤销不需要等待safepoint。
//具体分析看下面第5点,返回状态只有两个:NOT_BIASED和BIAS_REVOKED
BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD);
((JavaThread*) THREAD)->set_cached_monitor_info(NULL);
...
return cond;
} else {
//6.通知VM去寻找safepoint进行撤销偏向锁
VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
VMThread::execute(&revoke);
return revoke.status_code();
}
}
//7.通知VM去寻找safepoint进行批量撤销偏向锁
VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,
(heuristics == HR_BULK_REBIAS),
attempt_rebias);
VMThread::execute(&bulk_revoke);
return bulk_revoke.status_code();
}
- 这里需要注意的点是下面的几个条件中只要return了,就说明已经撤销偏向锁了,这些都是不需要进行撤销计数器计数和等到safepoint再撤销偏向锁的。
- 对于计算hashCode为什么能进入这段代码,我们可以通过查看源码得到,oopDesc::identity_hash是多个计算hashCode的入口,说明我们调用一个对象的时候也会进到这里:
//oops/oop.inline.hpp#689
inline intptr_t oopDesc::identity_hash() {
markOop mrk = mark();
if (mrk->is_unlocked() && !mrk->has_no_hash()) {
//此时markword不属于锁状态且没计算过hashcode
return mrk->hash();
} else if (mrk->is_marked()) {
//此时markword属于GC标记,锁标记为11
return mrk->hash();
} else {
//此时markword属于锁状态,如果属于偏向锁,需要进行撤销
return slow_identity_hash();
}
}
通过继续查看源码得知,最后调用了下面的方法:
// runtime/synchronizer.cpp#603
intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
if (UseBiasedLocking) {
if (obj->mark()->has_bias_pattern()) {
//这里就传了false进去,说明需要进行立即撤销偏向锁
BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
obj = hobj() ;
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
}
...
}
从上面的分析知道,在计算hashcode后,就不能再进行获取偏向锁了,因为偏向锁在markword上需要记录线程id,而hashcode的信息也是存在markword中的,这就产生了冲突,所以为了存用户需要的hashcode,不得不撤销偏向锁并进行锁膨胀。
- 这里说明一下,这个方法返回的标识有以下三种:
enum Condition { //runtime/biasedLocking.hpp#161
NOT_BIASED = 1,
BIAS_REVOKED = 2,
BIAS_REVOKED_AND_REBIASED = 3
};
- HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);我们来看一下这里面是怎么去实现区分4种不同的情况:
// runtime/biasedLocking.cpp#268
static HeuristicsResult update_heuristics(oop o, bool allow_rebias) {
markOop mark = o->mark();
//如果此时的markword不是属于偏向模式,则直接返回不偏向
if (!mark->has_bias_pattern()) {
return HR_NOT_BIASED;
}
//首先拿到当前时间和上一次进行bulk revoke或bulk rebias
//的时间(在bulk_revoke_or_rebias_at_safepoint中设置),
//再从klass中拿到撤销计数器的值。
Klass* k = o->klass();
jlong cur_time = os::javaTimeMillis();
jlong last_bulk_revocation_time = k->last_biased_lock_bulk_revocation_time();
int revocation_count = k->biased_lock_revocation_count();
if ((revocation_count >= BiasedLockingBulkRebiasThreshold) &&
(revocation_count < BiasedLockingBulkRevokeThreshold) &&
(last_bulk_revocation_time != 0) &&
(cur_time - last_bulk_revocation_time >= BiasedLockingDecayTime)) {
//满足上面的所有条件,即撤销计数器值要大于
//BiasedLockingBulkRebiasThreshold(默认为20,可设置)和小于
//BiasedLockingBulkRevokeThreshold(默认为40,可设置)和上一次
//进行批量重偏向或撤销的时间不为0和当前时间-上一次大于等于
//BiasedLockingDecayTime(默认为4000,可设置),就把计数器的值置零,
//重新计算,原因是经过一次批量重偏向说明这个类的偏向锁存在问题,而经过一定的
//时间,即BiasedLockingDecayTime后,如果还没有触发批量撤销动作,说明这
//个类的偏向锁有所改善,觉得可以继续使用偏向锁,所以就开始重新计算撤销次数。
k->set_biased_lock_revocation_count(0);
revocation_count = 0;
}
//撤销计数器加1,代表此时进行了一次撤销动作,这里只是代表了来到这里的情况才+1,
//并不是所有的撤销操作都+1,比如hashCode的计算就不会执行到这一步
if (revocation_count <= BiasedLockingBulkRevokeThreshold) {
revocation_count = k->atomic_incr_biased_lock_revocation_count();
}
//计数器到达阀值20,准备触发一次批量重偏向
if (revocation_count == BiasedLockingBulkRevokeThreshold) {
return HR_BULK_REVOKE;
}
//计数器到达阀值40,准备触发一次批量撤销
if (revocation_count == BiasedLockingBulkRebiasThreshold) {
return HR_BULK_REBIAS;
}
//到这里默认都返回撤销当前偏向
return HR_SINGLE_REVOKE;
}
注意:上面的revoke_and_rebias()方法中只要在update_heuristics()方法执行前返回的操作都没有进行撤销计数器+1!
补充点:
- 批量重偏向可执行多次,但批量撤销只可能执行一次。
- 上面的只是撤销偏向的准备,属于撤销前的状态描述,决定去执行哪种撤销。
- 真正的撤销动作在safepoint中执行,此时所有java线程都属于停止状态。除了一种情况,就是下面第4点描述的情况,这时不需要去
- BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD);这里调用了一个revoke_bias()方法,这个方法执行完后就会有两个结果,一个是NOT_BIASED,一个是BIAS_REVOKED,两个都最后都会直接调用到轻量级锁的过程。这里的撤销是不用等待safepoint的。
// runtime/biasedLocking.cpp#146
static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread) {
markOop mark = obj->mark();
//如果此时的markword不是属于偏向模式,则直接返回不偏向
if (!mark->has_bias_pattern()) {
...
return BiasedLocking::NOT_BIASED;
}
//把GCage都设置都biased_prototype和unbiased_prototype中
//
uint age = mark->age();
//创建两个mark word,一个是匿名偏向模式“101”,一个是无锁模式“001”
markOop biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age);
markOop unbiased_prototype = markOopDesc::prototype()->set_age(age);
...
JavaThread* biased_thread = mark->biased_locker();
if (biased_thread == NULL) {
//锁对象是匿名偏向的,可以为了计算hashcode,而在这里直接撤销偏向锁,
//设置markword为无锁状态
if (!allow_rebias) {
obj->set_mark(unbiased_prototype);
}
...
return BiasedLocking::BIAS_REVOKED;
}
//判断正在偏向的线程是否还活着
bool thread_is_alive = false;
//通过看当前线程是否是已偏向的线程或者通过遍历记录线程队列去查看偏向线程是否活着
if (requesting_thread == biased_thread) {
thread_is_alive = true;
} else {
for (JavaThread* cur_thread = Threads::first(); cur_thread != NULL; cur_thread = cur_thread->next()) {
if (cur_thread == biased_thread) {
thread_is_alive = true;
break;
}
}
}
if (!thread_is_alive) {
//如果线程已经不在了,就直接通过设置markword为无锁状态,由于传进来的参数
//allow_rebias=false
if (allow_rebias) {
obj->set_mark(biased_prototype);
} else {
obj->set_mark(unbiased_prototype);
}
...
return BiasedLocking::BIAS_REVOKED;
}
//如果线程还活着,就去遍历线程栈中所有的lock record
GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);
BasicLock* highest_lock = NULL;
for (int i = 0; i < cached_monitor_info->length(); i++) {
MonitorInfo* mon_info = cached_monitor_info->at(i);
//如果能找到对应的lock record说明偏向的线程还在执行同步代码块的代码
if (mon_info->owner() == obj) {
...
//需要升级为轻量级锁,直接修改偏向线程栈中的Lock Record。
//为了处理锁重入的,在这里将Lock Record的Displaced Mark Word
//设置为null,第一个Lock Record会在下面的代码中再处理
markOop mark = markOopDesc::encode((BasicLock*) NULL);
highest_lock = mon_info->lock();
highest_lock->set_displaced_header(mark);
} else {
...
}
}
if (highest_lock != NULL) {
//修改第一个Lock Record为无锁状态,
//然后将obj的mark word设置为执行该Lock Record的指针
highest_lock->set_displaced_header(unbiased_prototype);
obj->release_set_mark(markOopDesc::encode(highest_lock));
} else {
//走到这里说明偏向线程已经不在同步块中了
if (allow_rebias) {
obj->set_mark(biased_prototype);
} else {
//将mark word设置为无锁状态
obj->set_mark(unbiased_prototype);
}
}
return BiasedLocking::BIAS_REVOKED;
}
- 注意:当调用锁对象的hashcode()或者System.identityHashCode()方法都会导致该对象的偏向锁或轻量级锁升级。因为在Java中一个对象的hashcode是在调用这两个方法时才生成的,如果是无锁状态则存放在mark word中,如果是重量级锁则存放在对应的monitor中,而偏向锁是没有地方能存放该信息的,所以必须升级。
这个方法是撤销的主要代码,大致根据当前线程是否存活去判断下一步应该怎么做。如果线程已经不存在了,那么就直接将锁对象的markword设置为无锁状态;而线程还存在,则会寻找该线程栈中是否存在对应的markword,如果找到了,说明还在执行同步代码,则需要把该线程的锁升级为轻量级锁。找不到则说明已经执行完同步代码,但还持有着偏向锁,所以直接设置为无锁状态,后面访问时直接升级为轻量级锁。 - 这里就已经开始通知VM线程要进行撤销偏向锁了。
// runtime/biasedLocking.cpp#633
void BiasedLocking::revoke(GrowableArray<Handle>* objs) {
...
if (objs->length() == 0) {
return;
}
//去到VM线程执行,等待所有线程到达安全点,进行撤销操作
VM_RevokeBias revoke(objs, JavaThread::current());
VMThread::execute(&revoke);
}
- 这里直接拿到VM_BulkRevokeBias对象,而它是通过VM线程去调用它的doit方法进行批量重偏向或撤销操作的:
// runtime/biasedLocking,cpp#511
public:
//初始化
VM_BulkRevokeBias(Handle* obj, JavaThread* requesting_thread,
bool bulk_rebias,
bool attempt_rebias_of_object)
: VM_RevokeBias(obj, requesting_thread)
, _bulk_rebias(bulk_rebias)
, _attempt_rebias_of_object(attempt_rebias_of_object) {}
virtual VMOp_Type type() const { return VMOp_BulkRevokeBias; }
virtual bool doit_prologue() { return true; }
//等待VM线程调用
virtual void doit() {
//在safepoint点进行bulk revoke或rebias处理
_status_code = bulk_revoke_or_rebias_at_safepoint((*_obj)(), _bulk_rebias, _attempt_rebias_of_object, _requesting_thread);
clean_up_cached_monitor_info();
}
};
现在就可以看一下bulk_revoke_or_rebias_at_safepoint里面干了些什么:
`c++
// runtime/biasedLocking.cpp#321
static BiasedLocking::Condition bulk_revoke_or_rebias_at_safepoint(oop o,bool bulk_rebias,bool attempt_rebias_of_object,JavaThread* requesting_thread) {
…
//这里就设置了当前时间记录到最新一次进行批量重偏向或撤销的时间
jlong cur_time = os::javaTimeMillis();
o->klass()->set_last_biased_lock_bulk_revocation_time(cur_time);
Klass* k_o = o->klass();
Klass* klass = k_o;
if (bulk_rebias) {
//批量重偏向操作,说明此时已达到阀值(默认20)
if (klass->prototype_header()->has_bias_pattern()) {
//属于偏向模式,并把klass的prototype_header的epoch+1;
//由于epoch只有两位,如果超过11,通过移位运算就会变为从00
int prev_epoch = klass->prototype_header()->bias_epoch();
klass->set_prototype_header(klass->prototype_header()->incr_bias_epoch());
int cur_epoch = klass->prototype_header()->bias_epoch();
//遍历当前的所有线程,找到线程栈中的当前类的正在偏向的对象(即存在lock
//record中的对象),并统一更新epoch值
for (JavaThread* thr = Threads::first(); thr != NULL; thr = thr->next()) {
GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(thr);
for (int i = 0; i < cached_monitor_info->length(); i++) {
MonitorInfo* mon_info = cached_monitor_info->at(i);
oop owner = mon_info->owner();
markOop mark = owner->mark();
if ((owner->klass() == k_o) && mark->has_bias_pattern()) {
...
owner->set_mark(mark->set_bias_epoch(cur_epoch));
}
}
}
}
//强制进行撤销操作,此操作上面第5点已讲,可上去查看分析
revoke_bias(o, attempt_rebias_of_object && klass->prototype_header()->has_bias_pattern(), true, requesting_thread);
} else {
...
//此时属于批量撤销操作,首先重新设置klass的prototype_header,
//设置为“00...[29]001”
klass->set_prototype_header(markOopDesc::prototype());
//遍历当前的所有线程,找到线程栈中的当前类的正在偏向的对象(即存在lock record中的对象),并统一进行撤销操作
for (JavaThread* thr = Threads::first(); thr != NULL; thr = thr->next()) {
GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(thr);
for (int i = 0; i < cached_monitor_info->length(); i++) {
MonitorInfo* mon_info = cached_monitor_info->at(i);
oop owner = mon_info->owner();
markOop mark = owner->mark();
if ((owner->klass() == k_o) && mark->has_bias_pattern()) {
revoke_bias(owner, false, true, requesting_thread);
}
}
}
//强制对当前已偏向的线程进行撤销操作
revoke_bias(o, false, true, requesting_thread);
}
...
BiasedLocking::Condition status_code = BiasedLocking::BIAS_REVOKED;
if (attempt_rebias_of_object &&
o->mark()->has_bias_pattern() &&
klass->prototype_header()->has_bias_pattern()) {
//重偏向操作
markOop new_mark = markOopDesc::encode(requesting_thread, o->mark()->age(),klass->prototype_header()->bias_epoch());
o->set_mark(new_mark);
status_code = BiasedLocking::BIAS_REVOKED_AND_REBIASED;
...
}
...
return status_code;
}
上面分析完后,我们回到一开始的如果vm直接进去调用
revoke_at_safepoint()方法,方法如下:
void BiasedLocking::revoke_at_safepoint(Handle h_obj) {
assert(SafepointSynchronize::is_at_safepoint(), "must only be called while at safepoint");
oop obj = h_obj();
//上面已经描述的很清楚了,可以回顾上面的第4点
HeuristicsResult heuristics = update_heuristics(obj, false);
if (heuristics == HR_SINGLE_REVOKE) {
revoke_bias(obj, false, false, NULL);
} else if ((heuristics == HR_BULK_REBIAS) ||
(heuristics == HR_BULK_REVOKE)) {
bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);
}
clean_up_cached_monitor_info();
}
到这里就已经对整个偏向锁的撤销分析完了,挺多知识需要消化的,不过大概知道每一种发生了什么,大致过程是怎么样的,每种情况的去向就差不多了。
获取轻量级锁
我们知道,当偏向锁撤销后,如果没有进行过重偏向,则会导致锁升级为轻量级锁。进入轻量级锁的地方有两个,bytecodeInterpreter.cpp#1891和synchronizer.cpp#226,其中第一个已经在一开始讲解bytecodeInterpreter中说过了,如果两个线程交替进入同步块,是不会进入到InterpreterRuntime::monitorenter中的,会直接通过CAS把锁对象的markword替换到lock record的displaced header中,如果成功说明已经成功获取轻量级锁了。
下面就来分析第二个进入轻量级锁的地方:
// synchronizer.cpp#226
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
...
// 判断是否是无锁状态
if (mark->is_neutral()) {
// Anticipate successful CAS -- the ST of the displaced mark must
// be visible <= the ST performed by the CAS.
// 当前为无锁状态,需要获取轻量级锁
lock->set_displaced_header(mark);
// 通过CAS把锁对象的markword指向lock record的首地址
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
// 成功说明已成功获取轻量级锁
TEVENT (slow_enter: release stacklock) ;
return ;
}
} else
//判断是否当前是轻量级锁,如果是,说明锁重入了
if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
...
// 轻量级锁重入
lock->set_displaced_header(NULL);
return;
}
...
#endif
//设置lock record的displaced_header的所标记为“11”
lock->set_displaced_header(markOopDesc::unused_mark());
//下面进行自旋锁获取,如果通过一定次数还是没能获取轻量级锁,
//这时候需要膨胀为重量级锁,膨胀前,设置Displaced Mark Word为一个特殊值,代表该锁正在用一个重量级锁的monitor
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
上面主要过程是:首先尝试通过CAS去获取轻量级锁,如果CAS失败了,就会通过调用ObjectSynchronizer::inflate()方法尝试去自旋获取锁,还是失败就升级为重量级锁。
锁膨胀
下面继续分析ObjectSynchronizer::inflate()方法:
// synchronizer.cpp#1194
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
...
//通过无意义的循环实现自旋操作
for (;;) {
const markOop mark = object->mark() ;
...
// mark是以下状态中的一种:
// * Inflated(重量级锁状态) - 直接返回
// * Stack-locked(轻量级锁状态) - 膨胀
// * INFLATING(膨胀中) - 忙等待直到膨胀完成
// * Neutral(无锁状态) - 膨胀
// * BIASED(偏向锁) - 非法状态,在这里不会出现
// CASE: inflated
if (mark->has_monitor()) {
//已经是重量级锁状态,直接返回
ObjectMonitor * inf = mark->monitor() ;
...
return inf ;
}
// CASE: inflating
if (mark == markOopDesc::INFLATING()) {
//正在膨胀中,说明另外一个线程正在进行膨胀,continue重试
TEVENT (Inflate: spin while INFLATING) ;
//在该方法中会进行spin/yield/park等操作完成自旋动作
ReadStableMark(object) ;
continue ;
}
// CASE: Stack-locked
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 ;
//CAS将锁对象的mark word设置为INFLATING(0)状态
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
if (cmp != mark) {
omRelease (Self, m, true) ; //释放ObjectMonitor
continue ; // 当前有竞争,重试
}
//栈中的displaced_mark_word
markOop dmw = mark->displaced_mark_helper() ;
...
//设置monitor字段
m->set_header(dmw) ;
//owner为lock record
m->set_owner(mark->locker());
m->set_object(object);
...
//将锁对象的markword设置为重量级锁状态
object->release_set_mark(markOopDesc::encode(m));
...
return m ;
}
// CASE: neutral
//分配和初始化ObjectMonitor对象
ObjectMonitor * m = omAlloc (Self) ;
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 ;
//用CAS替换对象头的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 ;
}
}
这各部分通过自旋进行膨胀操作,根据锁对象的锁状态进行不同的处理:
- 已经是重量级状态,说明膨胀已经完成,直接返回
- 如果是轻量级锁则需要进行膨胀操作
- 如果是膨胀中状态,则进行忙等待
- 如果是无锁状态则需要进行膨胀操作
其中膨胀操作只用于第2种和第4种状态。下面分别分析两种膨胀过程:
轻量级锁膨胀过程如下:
- 调用omAlloc()分配一个ObjectMonitor对象,在omAlloc方法中会先从线程私有的monitor集合omFreeList中分配对象,如果omFreeList中已经没有monitor对象,则从JVM全局的gFreeList中分配一批monitor到omFreeList中。
- 初始化monitor对象
- 将状态设置为膨胀中(INFLATING)状态
- 设置monitor的header字段为displaced mark word,owner字段为Lock Record,obj字段为锁对象
- 设置锁对象头的mark word为重量级锁状态,指向第一步分配的monitor对象
无锁状态膨胀过程如下:
- 调用omAlloc分配一个ObjectMonitor对象(以下简称monitor)
- 初始化monitor对象
- 设置monitor的header字段为mark word,owner字段为null,obj字段为锁对象
- 设置锁对象头的mark word为重量级锁状态,指向第一步分配的monitor对象
获取重量级锁
锁膨胀的过程实际上是获得一个ObjectMonitor对象监视器,而真正抢占锁的逻辑,在 ObjectMonitor::enter方法里面。
// objectMonitor.cpp#317
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) {
//锁重入状态
_recursions ++ ;
return ;
}
//当前线程是之前持有轻量级锁的线程。由轻量级锁膨胀且第一次调用enter方法,那cur是指向lock record的指针
if (Self->is_lock_owned ((address)cur)) {
...
//重置锁重入计数为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);
}
...
}
上面大致分为3个过程:
- 如果当前是无锁状态、锁重入、当前线程是之前持有轻量级锁的线程则进行简单操作后返回
- 先自旋尝试获得锁,这样做的目的是为了减少执行操作系统同步操作带来的开销
- 调用EnterI方法获得锁或阻塞
在了解EnterI()之前,先了解一下ObjectMonitor的结构和整个获取锁的过程原理:
一个ObjectMonitor对象主要关键字段有:cxq(ContentionList)、EntryList、WaitSet和owner。其中cxq、EntryLsit、WaitSet都是由ObjectWaiter组成的链表结构,owner指向持有锁的线程。
通过上面的图我们来了解一下整个过程:
- 当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个ObjectWaiter对象插入到cxq的队列的队首,然后调用park函数挂起当前线程。在linux系统上,park函数底层调用的是gclib库的pthread_cond_wait,JDK的ReentrantLock底层也是用该方法挂起线程的。具体关于linux同步机制可参考这位大神的文章关于同步的一点思考-下,linux内核级同步机制–futex
- 当线程释放锁时,会从cxq或EntryList中挑选一个线程唤醒,被选中的线程叫做Heir presumptive即假定继承人(应该是这样翻译),就是图中的Ready Thread,假定继承人被唤醒后会尝试获得锁,但synchronized是非公平的,所以假定继承人不一定能获得锁(这也是它叫”假定”继承人的原因)。
- 如果线程获得锁后调用Object#wait方法,则会将线程加入到WaitSet中,当被Object#notify唤醒后,会将线程从WaitSet移动到cxq或EntryList中去。需要注意的是,当调用一个锁对象的wait或notify方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。
下面就继续看一下EnterI()方法来了解重量级锁是如何获取的:
void ATTR ObjectMonitor::EnterI (TRAPS) {
Thread * Self = THREAD ;
//尝试获取锁
if (TryLock (Self) > 0) {
...
return ;
}
DeferredInitialize () ;
//自旋
if (TrySpin (Self) > 0) {
...
return ;
}
...
//将线程封装成ObjectWaiter
ObjectWaiter node(Self) ;
Self->_ParkEvent->reset() ;
node._prev = (ObjectWaiter *) 0xBAD ;
node.TState = ObjectWaiter::TS_CXQ ;
//将ObjectWaiter节点插入到_cxq单向链表的头部
ObjectWaiter * nxt ;
for (;;) {
node._next = nxt = _cxq ;
//CAS方式插入
if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
//CAS失败,尝试重新获得锁,降低插入到_cxp队列的频率
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 ;
...
//重新尝试将_Responsible设置为当前线程
if ((SyncFlags & 2) && _Responsible == NULL) {
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
// park self
if (_Responsible == Self || (SyncFlags & 1)) {
//当前线程是_Responsible时,调用的是有时间参数的park方法
TEVENT (Inflated enter - park TIMED) ;
Self->_ParkEvent->park ((jlong) RecheckInterval) ;
//递增8倍时间,最大为1000
RecheckInterval *= 8 ;
if (RecheckInterval > 1000) RecheckInterval = 1000 ;
} else {
//否则直接调用park挂起当前线程
TEVENT (Inflated enter - park UNTIMED) ;
Self->_ParkEvent->park() ;
}
if (TryLock(Self) > 0) break ;
TEVENT (Inflated enter - Futile wakeup) ;
if (ObjectMonitor::_sync_FutileWakeups != NULL) {
ObjectMonitor::_sync_FutileWakeups->inc() ;
}
++ nWakeups ;
if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
Self->_ParkEvent->reset() ;
OrderAccess::fence() ;
}
//在释放锁时,_succ会被设置为EntrySet或_cxq中的一个线程
if (_succ == Self) _succ = NULL ;
// Invariant: after clearing _succ a thread *must* retry _owner before parking.
OrderAccess::fence() ;
}
...
//执行到这里表示已经获取锁了
UnlinkAfterAcquire (Self, &node) ;
if (_succ == Self) _succ = NULL ;
...
//
if (_Responsible == Self) {
_Responsible = NULL ;
OrderAccess::fence();
}
if (SyncFlags & 8) {
OrderAccess::fence() ;
}
return ;
}
上面主要分为3步:
- 将当前线程插入到cxq队列的队头
- 然后park当前线程
- 当被唤醒后再尝试获取锁
_Responsible:当竞争发生时,选取一个线程作为_Responsible,_Responsible线程调用的是有时间限制的park方法,其目的是防止出现搁浅现象。
_succ:在线程释放锁是被设置,其含义是Heir presumptive,也就是上面说的假定继承人。
获取重量级锁这一部分由于之前没有怎么区了解Linux同步机制,所以大部分是参考这篇文章的死磕Synchronized底层实现–重量级锁。
那么讲到这里对于整个monitorenter指令的执行过程和每种情况都分析了一遍,总体来说,还是挺复杂的。不过一步步分析下来整体思路还是好的。下面就继续了解monitorexit指令吧。
monitorexit指令解析
首先monitorexit的入口在bytecodeInterpreter.cpp#1911中,下面就直接看一下这个方法:
// bytecodeInterpreter.cpp#1911
CASE(_monitorexit): {
oop lockee = STACK_OBJECT(-1);
CHECK_NULL(lockee);
//获取当前线程栈的lock record指针
BasicObjectLock* limit = istate->monitor_base();
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
//从低往高遍历栈中的lock record
while (most_recent != limit ) {
if ((most_recent)->obj() == lockee) {
//找到栈的lock record中存当前锁对象的记录,然后设置lock record的obj
//为null,就相当于释放了,可以重新利用该record
BasicLock* lock = most_recent->lock();
markOop header = lock->displaced_header();
most_recent->set_obj(NULL);
//判断是否当前的锁是否是偏向锁,如果是偏向模式的话,只需要释放lock
//record就说明偏向锁已经释放了(不等于撤销),否则需要走释放轻量级锁或 //重量级锁的流程
if (!lockee->mark()->has_bias_pattern()) {
bool call_vm = UseHeavyMonitors;
//header!=null说明不是锁重入,则需要通过CAS将
//displaced_mark_word设置会锁对象的markword中
if (header != NULL || call_vm) {
//CAS成功则释放轻量级锁成功
if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
//CAS失败或者禁用偏向和轻量级锁,会先将obj还原,
//再继续调用InterpreterRuntime::monitorexit释放锁
most_recent->set_obj(lockee);
CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
}
}
}
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
}
//当前不是锁对象对应的lock record,指向下一个lock record的位置
most_recent++;
}
// Need to throw illegal monitor state exception
CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);
ShouldNotReachHere();
}
如果属于单线程或多线程同步,释放锁就是上面的流程,不会进到InterpreterRuntime::monitorexit中(设置+UseHeavyMonitors除外)。
那么现在继续看多线程竞争下释放锁的流程:
// InterpreterRuntime.cpp#627
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
...
#endif
Handle h_obj(thread, elem->obj());
...
//如果取到的obj为空或者是无锁状态,则抛异常,不合理
if (elem == NULL || h_obj()->is_unlocked()) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
//最后这里肯定是释放锁成功的,所以需要释放lock record
elem->set_obj(NULL);
...
IRT_END
上面比较简单,直接继续看ObjectSynchronizer::slow_exit,发现真正调用的地方是ObjectSynchronizer::fast_exit:``
// synchronized.cpp#185
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
...
markOop dhw = lock->displaced_header();
markOop mark ;
if (dhw == NULL) {
//如果lock record的displacedmarkword为空说明是重入锁,处理一些判断就返回
mark = object->mark() ;
...
return ;
}
mark = object->mark() ;
//如果锁对象的markword指针指向lock record的头说明是轻量级锁,通过CAS把
//displaced_header的值重新取回锁对象的markword中
if (mark == (markOop) lock) {
...
if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
//成功释放轻量级锁
TEVENT (fast_exit: release stacklock) ;
return;
}
}
//释放失败,则先进行锁膨胀操作,然后再进行ObjectMonitor的exit释放锁
ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}
上面首先判断是否是锁重入,是则什么都不做,然后再判断是不是轻量级锁,是则替换markword,否则膨胀为重量级锁并调用exit方法。
由于inflate方法已经在上面进行了分析,这里就直接分析exit方法,查看重量级锁是如何进行释放的:
// objectMonitor.cpp#955
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)) {
...
//把_owner指向当前线程,并重置重入计数为0
_owner = THREAD ;
_recursions = 0 ;
OwnerIsThread = 1 ;
} else {
// 异常情况:当前不是持有锁的线程
...
if (false) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
return;
}
}
//重入计数器还不为0,则计数器-1后返回
if (_recursions != 0) {
_recursions--;
TEVENT (Inflated exit - recursive) ;
return ;
}
//_Responsible设置为null
if ((SyncFlags & 4) == 0) {
_Responsible = NULL ;
}
...
for (;;) {
...
//Knob_ExitPolicy默认为0(#174)
if (Knob_ExitPolicy == 0) {
//设置_owner为null,即先释放锁,这时如果有其他线程进入同步块则能获得锁
//此时获取锁的策略是不公平的
OrderAccess::release_store_ptr (&_owner, NULL) ;
OrderAccess::storeload() ;
//此时如果没有等待的线程或已经有假定继承人
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
//此时没有等待的线程或_succ不为null,说明当前线程不需要唤醒任何线程
TEVENT (Inflated exit - simple egress) ;
return ;
}
TEVENT (Inflated exit - complex egress) ;
//要执行后面的操作需要重新获取锁,CAS设置_owner为当前线程
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
return ;
}
TEVENT (Exit - Reacquired) ;
} else {
//不走这段代码
...
}
...
ObjectWaiter * w = NULL ;
//根据QMode的不同会走不同的唤醒策略,默认为0(#181)
int QMode = Knob_QMode ;
if (QMode == 2 && _cxq != NULL) {
//QMode为2:cxq中的线程有更高的优先级,直接唤醒cxp的队头线程
//把w指向_cxp的队头指针,即最先的线程
w = _cxq ;
...
ExitEpilog (Self, w) ;
return ;
}
if (QMode == 3 && _cxq != NULL) {
//QMode为3:将_cxq中的元素插入到EntryList的末尾
w = _cxq ;
//把_cxq的指针指向null,清空所有waiter
for (;;) {
...
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
...
//把w中的所有waiter都设置前驱节点
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
...
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
//把w通过前驱节点全部插入到_EntryList中
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 ;
}
}
if (QMode == 4 && _cxq != NULL) {
//QMode为4:将_cxq所有的元素都插入到EntryList的队首中
w = _cxq ;
for (;;) {
...
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
...
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
...
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
if (_EntryList != NULL) {
q->_next = _EntryList ;
_EntryList->_prev = q ;
}
_EntryList = w ;
}
w = _EntryList ;
if (w != NULL) {
//如果EntryList不为空,则直接唤醒EntryList的队首元素
...
ExitEpilog (Self, w) ;
return ;
}
//EntryList为null,则处理_cxq中的waiter
w = _cxq ;
if (w == NULL) continue ;
//清空_cxq队列,只有w指向之前的_cxq队列的队头
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
...
if (QMode == 1) {
//QMode为1:将cxq中的元素转移到EntryList,并反转顺序
ObjectWaiter * s = NULL ;
ObjectWaiter * t = w ;
ObjectWaiter * u = NULL ;
while (t != NULL) {
...
t->TState = ObjectWaiter::TS_ENTER ;
u = t->_next ;
t->_prev = u ;
t->_next = s ;
s = t;
t = u ;
}
_EntryList = s ;
...
} else {
//QMode为0或QMode为2:将cxq中的元素转移到EntryList
//执行到这里说明EntryList肯定为null,所以直接把指针赋值给EntryList即可
_EntryList = w ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
...
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
}
//_succ不为null,说明已经有个假定继承人了,所以不需要当前线程去唤醒,减少上下文切换的比率
if (_succ != NULL) continue;
w = _EntryList ;
//唤醒EntryList的第一个元素
if (w != NULL) {
...
ExitEpilog (Self, w) ;
return ;
}
}
}
可以看到上面是根据设置不同的策略去进行不同的唤醒,但最终都会进入到ExitEpilog()这个方法进行唤醒线程,现在继续分析:<br />
// objectMonitor.cpp#1326
void ObjectMonitor::ExitEpilog (Thread * Self, ObjectWaiter * Wakee) {
...
// 释放步骤:
// 1.设置_succ为wakee,即设置假定继承人为传进来的waiter
// 2.插入两条内存屏障#loadstore和#storestore,保证同步操作
// 3.设置_owner为null,即设置当前暂时没有线程拥有锁
// 4.唤醒waiter线程
_succ = Knob_SuccEnabled ? Wakee->_thread : NULL ;
ParkEvent * Trigger = Wakee->_event ;
Wakee = NULL ;
OrderAccess::release_store_ptr (&_owner, NULL) ;
OrderAccess::fence() ;
if (SafepointSynchronize::do_call_back()) {
TEVENT (unpark before SAFEPOINT) ;
}
DTRACE_MONITOR_PROBE(contended__exit, this, object(), Self);
Trigger->unpark() ;
if (ObjectMonitor::_sync_Parks != NULL) {
ObjectMonitor::_sync_Parks->inc() ;
}
}
上面的注释中已经给出了步骤,然后到这里整个释放锁monitorexit指令的流程就大致结束了,其实释放锁的流程不难理解,只要对获取锁理解清楚了,说白了就是反过来操作一波。
Synchronized同步方法
Synchronized同步方法的入口是在ByteCodeInterpreter.cpp#682中,由于大部分逻辑都是和同步代码块一样,这里就不重复造轮子了。需要了解的可自行去看源码。
注意点:
修饰非static方法时锁对象是实例对象,修饰static方法时锁对象是Class对象。