上一篇:《Java并发系列(14)——synchronized之HotSpot源码解读(上)》
文章目录
10.5 轻量锁
10.5.1 偏向锁升级轻量锁
偏向锁针对的场景是,从头到尾只有一个线程对锁对象加锁。这种场景下 synchronized 性能损耗可以忽略不计,这也是为什么在前面的测试中看到,单线程情况下 Hashtable 有着可以比肩 HashMap 的性能。
而一旦出现多个线程对同一个对象加锁,这个对象就会从偏向锁升级(膨胀)为轻量锁。这个操作又被称为偏向撤销。
用 Java 代码来表达这个场景:
//thread1 先来
Thread thread1 = new Thread (() -> {
synchronized (lock) {
//...
}
});
thread1.start();
thread1.join();
//以上执行完,lock 已经偏向 thread1
//当前线程也来对 lock 加锁,会触发偏向撤销
synchronized (lock) {
//...
}
//以上执行完,lock 膨胀为轻量锁
这个加锁流程,首先还是 monitorenter 指令关于偏向锁的几个判断:
- 是否开启偏向锁?肯定是开启的;
- 是否偏向自己?肯定不是;
- 是否可偏向?当然可以,现在还是 101 状态,只不过偏向其它线程;
- 偏向是否过期?偏向过期后面再讲,这里还不会过期;
- 进入最后一个 else 判断,与偏向锁第一次加锁一样。
也就是下面这块代码:
这里与偏向锁第一次加锁的区别是:
- 偏向锁第一次加锁,mark word 是匿名偏向(00…00101),cas 操作会成功,success = true,加锁完成;
- 而这里的场景,锁对象已经偏向其它线程了,所以 cas 会失败。
于是这里就进入了 InterpreterRuntime::monitorenter(THREAD, entry) 方法:
如果开启了偏向锁,就会走 fast_enter 方法,进一步会走到 revoke_and_rebias 方法:
上图 HotSpot 源码中我已经加了中文注释,最后会创建一个 VM_RevokeBias 事件,VMThread 将会执行这个事件。
可以猜到这里应该是一个策略模式,具体执行逻辑应该还是定义在 VM_RevokeBias 里面,这里第一次我们还是尝试跟踪一下。
无论如何,总是要执行 evaluate 方法,继续跟踪:
接下来去找 VM_RevokeBias 事件的 doit 方法:
不出所料,果然又回到了 VM_RevokeBias 类,接下来调用 revoke_bias 方法。这个方法非常关键,也很长很复杂,这里把源码贴出来,仅保留主流程:
static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread, JavaThread** biased_locker) {
markOop mark = obj->mark();
//1.如果锁对象原本就不可偏向,走这里直接返回
if (!mark->has_bias_pattern()) {
//...
return BiasedLocking::NOT_BIASED;
}
uint age = mark->age();
//2.计算匿名偏向的 mark word,即 00...00101
markOop biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age);
//3.计算无锁不可偏向的 mark word,即 ...001
markOop unbiased_prototype = markOopDesc::prototype()->set_age(age);
//...
//4.当前偏向的线程
JavaThread* biased_thread = mark->biased_locker();
//5.如果原来是匿名偏向,走这里,比如计算了 hash code,把锁对象 mark word 改为无锁不可偏向
if (biased_thread == NULL) {
// Object is anonymously biased. We can get here if, for
// example, we revoke the bias due to an identity hash code
// being computed for an object.
if (!allow_rebias) {
obj->set_mark(unbiased_prototype);
}
return BiasedLocking::BIAS_REVOKED;
}
// Handle case where the thread toward which the object was biased has exited
//6.判断偏向的线程是否还活着
bool thread_is_alive = false;
//7.如果当前加锁的线程 == 偏向线程,那么一定活着
if (requesting_thread == biased_thread) {
thread_is_alive = true;
}
//8.否则,遍历所有存活线程,查看偏向线程是否还活着
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;
}
}
}
//9.如果偏向线程已经死了
if (!thread_is_alive) {
//10.如果可以重偏向,把锁对象的 mark word 改为匿名偏向
//偏向锁膨胀为轻量锁的场景,调用方传入的是 false
if (allow_rebias) {
obj->set_mark(biased_prototype);
}
//11.如果不可以重偏向,把锁对象的 mark word 改为无锁不可偏向
else {
obj->set_mark(unbiased_prototype);
}
//...
return BiasedLocking::BIAS_REVOKED;
}
// Thread owning bias is alive.
// Check to see whether it currently owns the lock and, if so,
// write down the needed displaced headers to the thread's stack.
// Otherwise, restore the object's header either to the unlocked
// or unbiased state.
GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);
BasicLock* highest_lock = NULL;
//12.如果偏向线程还活着,会走到这里,遍历偏向线程栈中的所有 lock record(MonitorInfo 存储的信息与 lock record 差别不大)
for (int i = 0; i < cached_monitor_info->length(); i++) {
MonitorInfo* mon_info = cached_monitor_info->at(i);
//13.如果还有 lock record 指向锁对象,表明偏向线程还没有退出 synchronized 代码块,因为 monitorexit 时会把这里的指针断开
if (mon_info->owner() == obj) {
//...
// Assume recursive case and fix up highest lock later
//将 lock record 中的 mark word 置空
markOop mark = markOopDesc::encode((BasicLock*) NULL);
highest_lock = mon_info->lock();
highest_lock->set_displaced_header(mark);
}
//...
}
//14.如果偏向线程还没有退出 synchronized
if (highest_lock != NULL) {
// Fix up highest lock to contain displaced header and point
// object at it
//15.lock record 的 mark word 设置为不可偏向无锁
highest_lock->set_displaced_header(unbiased_prototype);
// Reset object header to point to displaced mark.
// Must release storing the lock address for platforms without TSO
// ordering (e.g. ppc).
//16.锁对象的 mark word 中存入 lock record 的指针
obj->release_set_mark(markOopDesc::encode(highest_lock));
//...
}
//17.如果偏向线程已经退出 synchronized,与偏向线程已经死掉的情况做一样的操作
else {
//...
}
//...
return BiasedLocking::BIAS_REVOKED;
}
虽然已经在源码里面加了注释,还是再梳理一下。
撤销偏向锁的处理,包括以下几种情况:
- 根据锁对象 mark word,原本就不可偏向:什么都不用做;
- 如果可偏向但没有偏向:锁对象 mark word 改为无锁(001);
- 如果偏向线程还没有退出 synchronized:把它所有的 lock record 的 mark word 清除,并把最高位的那个 lock record 的 mark word 改为无锁,锁对象的 mark word 里面存入指向 lock record 的指针;
- 如果偏向线程已经退出 synchronized:如果还能偏向就把锁对象 mark word 改为匿名偏向(101),否则改为无锁;
- 如果偏向线程已经死了,同上。
偏向线程还没有退出 synchronized 块的情况暂时先不讲,这里讲的场景是已经退出 synchronized(见上面 Java 代码),并且实际上应该已经死掉了。
如上图所示,在第二个线程试图 synchronized 同一个锁对象,触发撤销偏向锁之前,锁对象的 mark word 记录的还是原偏向线程的 id 和 101,当然还有分代年龄之类的其它一些东西。原偏向线程因为已经退出 synchronized,所以它的线程栈里面没有任何 lock record 还指向锁对象(在偏向锁释放锁时讲过)。
既然原偏向线程已经不持有锁了,那偏向锁撤销就很简单,直接把锁对象 mark word 改为不可偏向的无锁状态(即 001,也可以说是轻量锁无锁)就行。
好了,到这一步,已经把原来的偏向锁撤销了,但是还没完,现在要开始对锁对象加锁。
注意上面 revoke_bias 方法的返回值是:BiasedLocking::BIAS_REVOKED。
现在源码返回到 ObjectSynchronizer::fast_enter 方法:
因为 revoke_and_rebias 方法返回的是 BiasedLocking::BIAS_REVOKED,所以不会从上面 return 出去,会进入 slow_enter 方法:
因为之前偏向锁撤销的时候,把锁对象的 mark word 改成了无锁,所以这里会走第一个 if,然后把 lock record 里面的 mark word 改成锁对象的无锁 mark word,再把锁对象的无锁 mark word 改成一个指向 lock record 里面 mark word(实际是 BasicLock 类型)的指针。但这个指针最后两位永远是 00,表示轻量锁,不清楚是怎么做到的。
最终轻量锁加锁完成就会变成下面这样:
10.5.2 释放锁
轻量锁释放,再去看 monitorexit 指令的实现:
轻量锁释放锁就比较简单了:
- 先把 lock record 指向锁对象的指针断开;
- 如果是重入的锁,到此结束;
- 如果是退出最后一个 synchronized 真正要释放锁了,就把 lock record 里面记录的 mark word 再还原到锁对象的 mark word 里面去。
可以看出,轻量锁释放锁性能比偏向锁稍差,多做一个 cas。
10.5.3 无锁直接加轻量锁
这里的场景是,开始就是无锁(001)——比如禁用了偏向锁或偏向锁已经膨胀为轻量锁——然后对无锁加轻量锁。
还是看 monitorenter 指令入口:
CASE(_monitorenter): {
oop lockee = STACK_OBJECT(-1);
// derefing's lockee ought to provoke implicit null check
CHECK_NULL(lockee);
// find a free monitor or one already allocated for this object
// if we find a matching object then we need a new monitor
// since this is recursive enter
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++;
}
if (entry != NULL) {
entry->set_obj(lockee);
int success = false;
uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;
markOop mark = lockee->mark();
intptr_t hash = (intptr_t) markOopDesc::no_hash;
// implies UseBiasedLocking
if (mark->has_bias_pattern()) {
//...
}
// traditional lightweight locking
//非偏向锁进这里
if (!success) {
//计算一个 001 mark word
markOop displaced = lockee->mark()->set_unlocked();
//lock record 里面的 mark word 设为上面 001 的 mark word
entry->lock()->set_displaced_header(displaced);
//JVM 指令:-XX:+UseHeavyMonitors
bool call_vm = UseHeavyMonitors;
//cas,如果锁对象的 mark word 是 001,将其替换为 lock record 指针,cas 失败就进去
if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
// Is it simple recursive case?
//上面 cas 失败,如果只是重入,把 lock record 里面的 mark word 再去掉
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
entry->lock()->set_displaced_header(