转自:https://www.aimoon.site/blog/2018/05/21/biased-locking/

比较复杂,简略见另一篇:https://www.cnblogs.com/twoheads/p/10148598.html

JVM中的java对象头

注意:在没有特殊说明的情况下,都是32 bits为例。

上一小节主要介绍了java中synchronized关键字的使用方法,而在这一小节中将介绍一下synchronized 在JVM中的实现基础——java对象头中的Mark Word

表1   Java对象头的长度

Mark Word存储对象的Mark Word信息-
Class Metadata Address存储指向对象存储类型的指针-
Array Length数组的长度只有数组对象有该属性

synchronized使用的锁是存放在Java对象头中的Mark Word中,OpenJDK中的markOop.hpp 头文件详细介绍了Mark Word的内容,下面将分析32 bits的JVM中的Mark Word的构成。

表2   32位JVM的Mark Word存储结构

轻量级锁指向栈中锁记录的指针00
无锁状态hash code分代年龄001
偏向锁Thread IDepoch分代年龄101
重量级锁指向监视器(monitor)的指针10
GC标记011

:最后两位为锁标记位,倒数第三位是偏向标记,如果是1表示是偏向锁;合并单元格的位数就是 该字段的位数,例如hash code共25(23+2)位。

另外,对于偏向锁,如果Thread ID = 0,表示未加锁

JVM锁的类型及对比

Java 1.6对synchronized进行了大幅度的优化,其性能也有了大幅度的提升。Java 1.6引入 “偏向锁”和“轻量级锁”的概念,减少了获得锁和释放锁的消耗。在Java 1.6之后,加上原有的重量 级锁,锁一共有4种状态,分别是:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态。锁只能 按照上述的顺序进行升级操作,锁只要升级之后,就不能降级。

下面将分别介绍一下偏向锁、轻量级锁和重量级锁,并探索一下偏向锁升级为轻量级锁(revoke bias) 的流程和轻量级锁升级为重量级锁(inflate)的流程。偏向锁、轻量级锁的状态转化及对象 Mark Word的关系如下图所示。JVM锁简介:偏向锁、轻量级锁和重量级锁-LMLPHP图片来源:Synchronization and Object Locking文章中的配图

图1   偏向锁、轻量级锁的状态转化及对象Mark Word的关系

1. 偏向锁

偏向锁是Java 1.6新添加的内容,并且是jdk默认启动的选项,可以通过-XX:-UseBiasedLocking 来关闭偏向锁。另外,偏向锁默认不是立即就启动的,在程序启动后,通常有几秒的延迟,可以通过命令 -XX:BiasedLockingStartupDelay=0来关闭延迟。

如果JVM支持偏向锁,那么将按照下图所示的流程分配对象,加偏向锁。JVM锁简介:偏向锁、轻量级锁和重量级锁-LMLPHP图片来源:Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing第3页的配图

图2   偏向锁中的Mark Word的状态转化图

注意:这是简化版的流程图,因为偏向锁的图中缺少了epoch字段。

1.1 偏向锁的加锁

如果JVM支持偏向锁,那么在分配对象时,分配一个可偏向而未偏向的对象(Mark Word的最后3位 为101,并且Thread ID字段的值为0)。

然后,当一个线程访问同步块并获取锁时,将通过CAS(Compare And Swap)来尝试将对象头中的 Thread ID字段设置为自己的线程号,如果设置成功,则获得锁,那么以后线程再次进入和退出 同步块时,就不需要使用CAS来获取锁,只是简单的测试一个对象头中的Mark Word字段中是否存储 着指向当前线程的偏向锁;如果使用CAS设置失败时,说明存在锁的竞争,那么将执行偏向锁的撤销操作 (revoke bias),将偏向锁升级为轻量级锁。

:代码请查看biasedLocking.cpp中的revoke_and_rebias方法

1.2 偏向锁的升级

下面结合代码(有缩减)分析一下偏向锁升级为轻量级锁的过程,这里暂时不考虑批量撤销偏向 (bulk revocation)的情况。详细代码请查看biasedLocking.cpp中的revoke_bias方法。 偏向锁的撤销操作需要在全局检查点(global safepoint)执行,在全局检查点上没有 线程执行字节码。

:偏向锁的撤销的入口函数是biasedLocking.cpp中的revoke方法, 之后会通过VMThread调用revoke_bias方法

static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias,
bool is_bulk, JavaThread* requesting_thread) {
markOop mark = obj->mark();
// 检查是否可偏向
if (!mark->has_bias_pattern()) {
return BiasedLocking::NOT_BIASED;
}
uint age = mark->age();
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) {
// 可偏向但是未偏向的情况
// 可能的使用场景为:因计算hash code而撤销偏向
if (!allow_rebias) {
obj->set_mark(unbiased_prototype);
}
return BiasedLocking::BIAS_REVOKED;
}
// 判断对象现在偏向的线程是否还存在
// 即对象头中Mark Word中Thread ID字段指向的线程是否存在
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) {
if (allow_rebias) {
obj->set_mark(biased_prototype);
} else {
obj->set_mark(unbiased_prototype);
}
return BiasedLocking::BIAS_REVOKED;
}
// 拥有偏向锁的线程仍然存活
// 检查该线程是否拥有锁:
// 如果拥有锁,那么需要升级为轻量级锁,然后将displaced mark word复制到线程栈中;
// 如果不再拥有锁,如果允许重偏向,那么将mark word中的Thread ID 重新置0;
// 如果不允许重偏向,那么将mark work设置为无锁状态,即最后两位为01 // cached_monitor_info 是该线程拥有的锁对象的信息,按照从加锁顺序的逆序排列
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);
if (mon_info->owner() == obj) {
// Assume recursive case and fix up highest lock later
markOop mark = markOopDesc::encode((BasicLock*) NULL);
highest_lock = mon_info->lock();
highest_lock->set_displaced_header(mark);
}
}
if (highest_lock != NULL) {
// 线程拥有锁
// Fix up highest lock to contain displaced header and point
// object at it
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).
obj->release_set_mark(markOopDesc::encode(highest_lock));
} else {
// 线程不再拥有锁
if (allow_rebias) {
obj->set_mark(biased_prototype);
} else {
obj->set_mark(unbiased_prototype);
}
}
return BiasedLocking::BIAS_REVOKED;
}

小结: 撤销偏向的操作需要在全局检查点执行。我们假设线程A曾经拥有锁(不确定是否释放锁), 线程B来竞争锁对象,如果当线程A不在拥有锁时或者死亡时,线程B直接去尝试获得锁(根据是否 允许重偏向(rebiasing),获得偏向锁或者轻量级锁);如果线程A仍然拥有锁,那么锁 升级为轻量级锁,线程B自旋请求获得锁。整个撤销偏向(revoke bias)的伪代码如下所示:

// 撤销流程的伪代码,在全局检查点执行该操作
if mark word 存储的不是可偏向状态:
return; // 如果不是偏向锁,那么没有撤销偏向的必要
else:
if Thread ID 指向的线程不存活:
if 允许重偏向:
退回可偏向但未偏向的状态 // Thread ID为0
else:
偏向撤销,变为无锁状态
else:
if Thread ID 指向的线程,仍然拥有锁:
升级为轻量级锁,将mark word复制到线程栈中,然后stack pointer指向最老的相关锁记录
else:
if 允许重偏向:
退回可偏向但未偏向的状态 // Thread ID为0
else:
偏向撤销,变为无锁状态

下图中的线程1演示了偏向锁初始化的流程,线程2演示了偏向锁撤销的流程:JVM锁简介:偏向锁、轻量级锁和重量级锁-LMLPHP图片来源:并发编程网-偏向锁的获得和偏向流程

图3   偏向锁的获得和偏向流程

1.3 epoch字段解释

上面介绍了那么多,还有一小部分的内容没有介绍,就是Mark Word中的epoch字段,epoch 主要是用于辅助批量重偏向(bulk rebiasing)和批量偏向撤销(bulk revocation)操作。 下面先讲一下什么叫做批量重偏向和批量偏向撤销。

意思就是说,某些操作,例如生产者和消费者队列,线程之间的竞争是不可避免的,这时候就不适合使用偏向锁。 我们需要识别这些情况,然后有选择的禁用偏向锁。此时显然不是只有一个锁,而是一个类或者该类的多个对象 作为锁,我们需要对这个类的所有对象都撤销偏向锁,这个优化操作,就叫做批量撤销偏向(bulk revocation`)

换句话说,就是在某一阶段,某个类的对象频繁发生偏向撤销的操作,当次数超过预定的阈值(threshold) 时,就批量撤销该类所有对象的偏向。

意思是说,在某些情况下,例如一个线程先分配了一定数量的对象(属于同一类型),然后对这些对象 执行同步操作,在该线程操作完成后,另一个线程接着执行同步操作,但是这两个线程的操作是顺序 执行的,不交互,我们就可以将前一个线程拥有的偏向重偏向到后一个线程,这个优化操作,就叫做 批量重偏向(bulk rebiasing)。

HotSpot通过Heuristics这个类来统计某一时间段内某个类对象的撤销偏向和重偏向的次数。

那么如何对某一特定数据类型的所有对象进行统计?HotSpot刚开始采用的方案是:遍历对象堆。 但是当堆变得比较大的时候,其扩展性就比较差,为了解决这个问题,引入了epoch的概念, 用epoch这个字段来表示偏向是否有效。

每一个可偏向的数据类型(类)都有自己响应的epoch值。注意,这个epoch number属于类。 此时,一个线程拥有偏向锁,指的是:对象头中的Mark Word的Thread ID字段指向该想线程, 并且对象实例的epoch字段和类的epoch字段值相同,如果不相同,可以认为该对象处于可偏向但 未偏向的状态。这样就不需要遍历整个对象堆,提高了效率。

注意:批量重偏向的操作,仍然需要在全局检查点(global safepoint)执行。 在全局检查点, 批量重偏向的额外操作如下:

  1. 将类中的epoch number加1。
  2. 然后,扫描所有的线程栈,定位所有仍然拥有锁的类的对象,然后将这些对象的epoch字段值, 更新为类的epoch字段值。基于Heuristics方法的考虑,这些对象的偏向可能被撤销。

另外,在引入epoch字段之后,获取偏向锁的流程如下面的伪代码所示:

// Biased locking acquisition supporting epoch-based bulk rebiasing and revocation
void lock (Object* obj, Thread* t) {
int lw = obj->lock_word;
if (lock_state(lw) == Biased && biasable(lw) == obj->class->biasable
&& bias_epoch(lw) == obj->class->epoch) {
if (lock_or_bias_owner == t->id) {
// current thread is the bias owner
return;
} else {
// need to revoke the object bias
revoke_bias(obj, t);
}
} else {
// normal locking/unlocking protocal,
// possibly with bias acquisition.
{
}

代码来源:引自论文Eliminating Synchronization-RelatedAtomic Operations with Biased Locking and Bulk Rebiasing第5页 listing 1 和 listing 2.

2. 轻量级锁

轻量级锁,也是JDK 1.6加入的新机制,之所以成为“轻量级”,是因为它不是使用操作系统互斥量来实现锁, 而是通过CAS操作,来实现锁。当线程获得轻量级锁后,可以再次进入锁,即锁是可重入(Reentrance Lock)的。

在轻量级锁的加锁阶段,如果线程发现对象头中Mark Word已经存在指向自己栈帧的指针,即线程 已经获得轻量级锁,那么只需要将0存储在自己的栈帧中(此过程称为递归加锁);在解锁的时候,如果发现锁记录的内容为0, 那么只需要移除栈帧中的锁记录即可,而不需要更新Mark Word。

2.1 轻量级锁的加锁和解锁过程

轻量级锁的加锁流程

线程在执行同步块之前,JVM会先在当前的线程的栈帧中创建所记录的空间,用于存储对象头中的 Mark Word的拷贝(官方称之为Displaced Mark Word),如图4所示。JVM锁简介:偏向锁、轻量级锁和重量级锁-LMLPHP图片来源:PPT The Hotspot Java Virtual Machine第 29 页

图4   轻量级锁CAS操作之前堆栈和对象的状态

然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录(Lock Record)的指针, 如图5所示。如果成功,当前线程获得轻量级锁,如果失败,虚拟机先检查当前对象头的Mark Word 是否指向当前线程的栈帧,如果指向,则说明当前线程已经拥有这个对象的锁,则可以直接进入同步块 执行操作,否则表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。当竞争线程的自旋次数 达到界限值(threshold),轻量级锁将会膨胀为重量级锁。

: 轻量级锁的获取过程请查看代码synchronizer.cpp中的slow_enter方法

JVM锁简介:偏向锁、轻量级锁和重量级锁-LMLPHP图片来源:PPT The Hotspot Java Virtual Machine第 30 页

图5   轻量级锁CAS操作之后堆栈和对象的状态

轻量级锁的解锁流程

轻量级锁解锁时,如果对象的Mark Word仍然指向着线程的锁记录,会使用CAS操作, 将Dispalced Mark Word替换到对象头,如果成功,则表示没有竞争发生。如果失败, 表示当前锁存在锁竞争,锁就会膨胀为重量级锁。

: 轻量级锁的获取过程请查看代码synchronizer.cpp中的fast_exit方法, 由synchronizer.cpp中的slow_exit方法调用。

2.2 轻量级锁膨胀为重量级锁

下面结合代码(有缩减)分析一下轻量级锁膨胀为重量级锁的过程。详细代码请查看 synchronizer.cpp中的inflate方法。返回的是一个monitor对象。

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
for (;;) {
const markOop mark = object->mark() ; // The mark can be in one of the following states:
// * Inflated - 直接返回
// * Stack-locked - 需要膨胀为重量级锁
// * INFLATING - 需要等待其他线程的锁膨胀操作完成
// * Neutral - 无锁状态,膨胀为重量级锁
// * BIASED - 非法状态,不可能存在 // CASE: inflated
// 如果已经膨胀为重量级锁,那么直接返回
if (mark->has_monitor()) {
ObjectMonitor * inf = mark->monitor();
return inf;
} // CASE: inflation in progress - inflating over a stack-lock.
// 这个状态很快就消失(transient)
// 有其他线程正在将轻量级锁转换为重量级锁,只有这个线程可以完成膨胀操作,
// 其他线程必须等待膨胀完成。虽然是自旋操作,但不会一直占用cpu资源,
// 每隔一段时间会通过os::NakedYield方法放弃cpu资源,或通过park方法挂起
if (mark == markOopDesc::INFLATING()) {
TEVENT (Inflate: spin while INFLATING) ;
ReadStableMark(object) ; // 自旋,等待膨胀完成
continue ;
} // CASE: stack-locked
// 可能该线程或者其他线程拥有轻量级锁,需要膨胀为重量级锁
if (mark->has_locker()) {
ObjectMonitor * m = omAlloc (Self) ;
// 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; markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(),
object->mark_addr(), mark) ;
if (cmp != mark) {
omRelease (Self, m, true) ;
continue ; // Interference -- just retry
} // 此时已经成功将mark word的状态改为INFLATING状态 // 从锁拥有者的栈帧中获取displaced mark word,然后设置monitor的各个字段,并返回
markOop dmw = mark->displaced_mark_helper() ;
m->set_header(dmw) ;
m->set_owner(mark->locker());
m->set_object(object); // Must preserve store ordering. The monitor state must
// be stable at the time of publishing the monitor address.
guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;
object->release_set_mark(markOopDesc::encode(m)); // Hopefully the performance counters are allocated on distinct cache lines
// to avoid false sharing on MP systems ...
if (ObjectMonitor::_sync_Inflations != NULL) {
ObjectMonitor::_sync_Inflations->inc() ;
}
TEVENT(Inflate: overwrite stacklock);
return m ;
} // CASE: neutral
// 将锁膨胀为重量级锁
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 ; 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 ;
// 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.
} // Hopefully the performance counters are allocated on distinct
// cache lines to avoid false sharing on MP systems ...
if (ObjectMonitor::_sync_Inflations != NULL){
ObjectMonitor::_sync_Inflations->inc();
}
TEVENT(Inflate: overwrite neutral);
return m;
}
}

锁膨胀要分为多种情况进行分析:

  • Inflated:说明已经是膨胀锁了,直接返回。
  • Inflating:说明其他线程在执行膨胀锁的操作,自旋等待膨胀完成,该状态是为了保证 只有一个线程执行膨胀锁的操作。
  • Stack-locked:由轻量级锁膨胀为重量级锁,首先通过CAS将Mark Word置0,即变为INFLATING 状态,然后先生成一个monitor,然后通过CAS将Mark Word指向monitor
  • Neutral:无锁状态,膨胀为重量级锁,先生成一个monitor,然后通过CAS将Mark Word指向monitor

: 锁膨胀完成后,返回对应的monitor,并且仅仅是返回monitor,不涉及线程竞争加锁和解锁。 真正的加锁操作是enter,解锁操作是exit,加锁和释放的代码在objectMonitor.cpp中。JVM锁简介:偏向锁、轻量级锁和重量级锁-LMLPHP图片来源:并发编程网-轻量级锁及膨胀流程图

图6   轻量级锁及膨胀流程图

3. 重量级锁

重量级锁(heavy weight lock),是使用操作系统互斥量(mutex)来实现的传统锁。 当所有对锁的优化都失效时,将退回到重量级锁。它与轻量级锁不同竞争的线程不再通过自旋来竞争线程, 而是直接进入堵塞状态,此时不消耗CPU,然后等拥有锁的线程释放锁后,唤醒堵塞的线程, 然后线程再次竞争锁。但是注意,当锁膨胀(inflate)为重量锁时,就不能再退回到轻量级锁。

重量级锁的加锁和释放的代码在objectMonitor.cpp中。 对于重量级锁的加锁和释放过程,我没有细看,因为涉及到底层的操作系统,看起来难道比较大。

4. 锁的对比

下面对偏向锁、轻量级锁和重量级锁进行比较:

表3   各种锁的优缺点及适用场景

偏向锁加锁和解锁不需要额外的消耗,与执行非同步方法仅存在纳秒级的差距如果线程间存在竞争,会带来额外的锁撤销的消耗适用于只有一个线程访问同步块的情况
轻量级锁竞争的线程不会堵塞,提高了程序的响应速度始终得不到锁的线程,使用自旋会消耗CPU追求响应时间,同步块执行速度非常块,只有两个线程竞争锁
重量级锁线程竞争不使用自旋,不会消耗CPU线程堵塞,响应时间缓慢追求吞吐量,同步块执行速度比较慢,竞争锁的线程大于2个

表格来源:《Java并发编程的艺术》第16页

hashcode()方法对偏向锁的影响

hashcode()方法Object类中,是一个native方法,用于计算对象的哈希值,可以和equla() 方法配合来比较对象是否相等。这里插一句,不同的对象可能会生成相同的hashcode值,所以不能 只根据hashcode值判断两个对象是否是同一对象,但是如果两个对象的hashcode值不等,则必定是两个不同的对象。 也就是说在比较对象是否为同一对象的时候,先判断两个对象的hashcode值是否真正相等, 如果不相等,那么肯定不是同一对象。如果相等,然后使用equals方法,如果相等就是同一个对象。

另外,我们通常认为该方法返回的是对象的内存地址,其实这种说法是不太准确的。例如,在 JDK 1.8中,这种方法是完全错误的。

synchroizer.cpp中的get_next_hash()方法中,介绍了几种计算hashcode的算法,如下所示(有删减):

// hashCode() generation
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
// 系统生成的随机数
value = os::random() ;
} else if (hashCode == 1) {
// 对对象的内存地址进行二次计算
// This can be useful in some of the 1-0 synchronization schemes.
intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
} else if (hashCode == 2) {
value = 1 ; // 用于敏感性测试
} else if (hashCode == 3) {
value = ++GVars.hcSequence ; // 自增的序列
} else if (hashCode == 4) {
value = cast_from_oop<intptr_t>(obj) ; // 对象的内存地址,转为int
} else {
// hashCode == 5
// Marsaglia's xor-shift scheme with thread-specific state
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
} value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash, "invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}

小结: 根据上面的代码,可以知道,有如下几种算法:

  • hashCode == 0: 系统生成的随机数
  • hashCode == 1: 对对象的内存地址进行二次计算
  • hashCode == 2: 硬编码1 (用于敏感性测试)
  • hashCode == 3: 一个自增的序列
  • hashCode == 4: 对象的内存地址,转为int
  • hashCode == 5: Marsaglia’s xor-shift scheme with thread-specific state

global.hpp中,我们可以看出,JDK 1.8使用的计算hashcode的算法为5, 也就是Marsaglia’s xor-shift scheme with thread-specific state (原谅我,不知道怎么翻译-_-//),所以hashcode的值与对象的内存地址,没有什么关系。

  product(intx, hashCode, 5, "(Unstable) select hashCode generation algorithm")

:可以使用参数-XX:hashCode=[0-5]来改变默认的算法。

下面结合代码(有删减,详细代码请查看synchroizer.cpp中的FastHashCode()方法), 分析一下,计算hashcode对锁状态的影响:

intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
if (UseBiasedLocking) {
// 如果使用偏向锁,那么撤销偏向锁
if (obj->mark()->has_bias_pattern()) {
// Box and unbox the raw reference just in case we cause a STW safepoint.
// 导致STW发生,进入全局检查点
Handle hobj (Self, obj) ;
BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
obj = hobj() ;
}
} // hashCode() is a heap mutator ...
ObjectMonitor* monitor = NULL;
markOop temp, test;
intptr_t hash;
markOop mark = ReadStableMark (obj); // 此时,已经不可能是偏向锁状态,那么还有3种状态:无锁,轻量级锁和重量级锁
if (mark->is_neutral()) {
// 此时是无锁状态,如果有hash code,那么直接返回,否则进行计算
hash = mark->hash(); // this is a normal header
if (hash) { // if it has hash, just return it
return hash;
}
hash = get_next_hash(Self, obj); // allocate a new hash code
temp = mark->copy_set_hash(hash); // merge the hash code into header
// 将计算的hash code使用CAS操作,拷贝到mark word中
test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
if (test == mark) {
return hash;
}
// 如果CAS操作失败,那么需要膨胀为重量级锁
} else if (mark->has_monitor()) {
// 此时是重量级锁
monitor = mark->monitor();
temp = monitor->header();
hash = temp->hash();
if (hash) {
return hash;
}
// Skip to the following code to reduce code size
} else if (Self->is_lock_owned((address)mark->locker())) {
// 此时是轻量级锁状态,查看displaced mark word中是否含有hash code
// 如果没有,那么膨胀为重量级锁
temp = mark->displaced_mark_helper();
hash = temp->hash(); // by current thread, check if the displaced
if (hash) { // header contains hash code
return hash;
}
} // 膨胀为重量值锁,然后设置hash code
monitor = ObjectSynchronizer::inflate(Self, obj); // 锁膨胀
// Load displaced header and check it has hash code
mark = monitor->header();
hash = mark->hash();
if (hash == 0) {
hash = get_next_hash(Self, obj);
temp = mark->copy_set_hash(hash); // merge hash code into header
test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
if (test != mark) {
hash = test->hash();
}
}
// We finally get the hash
return hash;
}

小结: 根据上面的代码可以知道,在HotSpot, 调用hashCode(), 或者System.identityHashCode() 方法会导致对象撤销偏向锁。换句话说,如果在明显存在竞争的情况下,例如生产者/消费者队列, 可以通过简单的调用hashCode()或者System.identityHashCode()方法来禁用偏向锁。

参考文献

  1. 深入理解java虚拟机, 周志明.
  2. Java并发编程的艺术, 方腾飞, 魏鹏, 程晓明.
  3. Kenneth Russell, David Detlefs. Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing
  4. Galo Navarro. How does the default hashCode() work?
  5. Paul Hohensee. The Hotspot Java Virtual Machine, p25-35.
  6. Thomas Kotzmann, Christian Wimmer. Synchronization and Object Locking
  7. Dave Dice. Biased Locking in HotSpot
  8. ross. Java并发之彻底搞懂偏向锁升级为轻量级锁
  9. OpenJDK 1.8 虚拟机部分源代码:markOop.hpp、 biasedLocking.cpp、 basicLock.cpp、 synchronizer.cppobjectMonitor.cpp
  10. 占小狼.JVM源码分析之synchronized实现
04-15 16:56