前面的简单铺垫对于懂得人看起来可能有些罗嗦,如果前面都知道怎么回事,可以直接 跳过1,2章节 往后看。
1 线程安全简介
使用Synchronized,是为了保证线程安全,那一般什么情况下会出现线程不安全的情况?通常满足一下两个条件:
- 多个线程操作共享资源。
- 对共享资源的操作会使得共享资源发生变化。
如有下面程序
public class SecurityDemo {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++){
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1000 ; j++){
count ++;
}
}
}).start();
System.out.println("i = " + i);
}
TimeUnit.SECONDS.sleep(1);
System.out.println(count);
}
}
程序中,创建了10个线程,对公共数据count进行累计,最终的到的结果 <= 10000,就是线程不安全的情况。
为了保证线程安全,我们最直接的想到的就是对线程加锁。加锁使得多个线程执行互斥,即同一时间内,只有获得锁的线程能够执行加锁的代码段。
加锁,第一时间想到的就是synchronized,synchronized最早是重量级锁的概念,所谓重量级锁,就是存在锁竞争的时候,一个线程获取锁之后,会将之后想要获取锁的线程挂起,而java线程和操作系统线程是一一映射的,所以对线程的挂起需要操作系统的支持,就需要进行用户态和内核态的转换,提别的消耗CPU,降低程序的执行效率。在1.6以后,对锁进行了优化。之所以优化,因为我们既要保证数据的安全性,又要保证性能。
2 synchronized的使用
synchronized使用很简单,根据修饰的位置不同,synchronized的使用分为三种情况:
2.1 修饰代码块
public class SynchronizedDemo {
public void fun(){
synchronized (this){
System.out.println();
}
}
}
上面这种修饰代码块的方式,锁的钥匙对象通常是使用者指定的,可以是任意的对。这里使用的是this,控制是当前实例当前方法的此代码块。这种方式可以精准,灵活的控制锁的粒度。
2.2 修饰实例方法
public class SynchronizedDemo {
public synchronized void fun(){
System.out.println();
}
}
这种方式,修饰实例方法,锁对象是对象实例本身this。控制范围是当前实例范围内整个方法。
3.3 修饰静态方法
public class SynchronizedDemo {
public synchronized static void fun(){
System.out.println();
}
}
这种方式,锁对象是SynchronizedDemo .class对象。控制的范围就是所有SynchronizedDemo 实例对象。
上面这三种方式,由于修饰的位置不同,所以锁对象不同,最终锁能够控制的范围就不同。
修饰位置 | 锁类型 | 控制范围 | 优缺点 |
---|---|---|---|
修饰代码块 | 对象锁 | 当前对象 | 可以指定锁对象,控制粒度细 |
修饰实例方法 | 对象锁 | 当前对象 | 不能指定锁对象,作用于整个方法 |
修饰静态方法 | 类锁 | 所有由当前class创建的对象 | 控制所有由当前class创建的对象,控制范围大 |
一般情况下,锁控制范围粒度越小,程序的性能会越高。但也有特殊情况,如可以把循环中的加锁加锁放于循环外,减少频繁的加锁解锁过程。
3 synchronized实现原理
synchronized使用很简单,使用的时候,只需要为synchronized指定一个锁对象,并且这个锁对象可以是任意对象,为了了解synchronized实现原理,第一个需要解决的问题就是:为什么每个对象都能够成为锁对象?
3.1 为什么每个对象都能够成为锁对象?
为了了解为什么每个对象都能够成为锁对象,我们首先要弄明白java对象在内存中的布局,即如何存储的。
3.1.1 java对象内存布局
在java程序执行的时候,对于每个java对象,在jvm中都有一个oopDesc对象与之相对应,我们可以通过oopDesc来了解java对象内存布局。 这个oopDesc定义在hotspot源码中的oop.hpp文件中,是jvm中java对象描述的基类。其有很多实现子类:
oopDesc子类 | 描述 | 源码中的描述 |
---|---|---|
instanceOopDesc | Java实例对象 | An instanceOop is an instance of a Java Class , Evaluating “new HashTable()” will create an instanceOop. |
arrayOopDesc | 数组基类, objArrayOopDesc、typeArrayOopDesc继承此类 | arrayOopDesc is the abstract baseclass for all arrays. It doesn’t declare pure virtual to enforce this because that would allocate a vtbl in each instance, which we don’t want. |
objArrayOopDesc | java对象数组 | An objArrayOop is an array containing oops, Evaluating “String arg[10]” will create an objArrayOop. |
typeArrayOopDesc | 基本类型数组 | A typeArrayOop is an array containing basic types (non oop elements), It is used for arrays of {characters, singles, doubles, bytes, shorts, integers, longs} |
oopDesc定义定义部分源码如下:
class oopDesc {
friend class VMStructs;
private:
volatile markOop _mark;
union _metadata {
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;
}
oopDesc定义中,有两个字段:
_mark : 用于存储对象的 HashCode、GC分代年龄,锁标记等信息,jvm中称之为Mark Word。这个稍候详细说明。
_metadata:是一个指向 Klass(类的class信息)的指针,是一个共用体( union )。也就是说它要么是 klass 字段要么是 compressedklass 。在64位jvm中,当 JVM 开启了-XX:+UseCompressedClassPointers( 表示启用 Klass 指针压缩选项, JVM 默认开启 )选项时使用 commpressedklass 来存储 Klass 引用,此时指针只占用4字节,否则使用 _klass 存储 Klass 引用。
在数组相关的 Oop 类中,除了上述两个数据成员外,还有一个 int 类型数据成员 length ,用于表示数组的长度。具体的详见hotspot源码定义中的arrayOop.hpp文件。
oopDesc定义的就是java对象的对象头的内容,即包括Mark Word和一个执行Class原数据的指针。除了对象头内容,对象中还有实例数据,实例数据在子类中定义,如instanceOopDesc类。对于java对象,无论是从父类继承的,还是自己定义的,都需要保存起来。
下面通过一个具体的例子来简单看一下各个区域的联系,如有如下代码:
public class OopDemo {
private static int count;
private String name;
public OopDemo(String name){
this.name = name;
count ++;
}
public static void main(String[] args) {
OopDemo oopDemoA = new OopDemo("oopDemoA");
OopDemo oopDemoB = new OopDemo("oopDemoB");
}
}
对应的各个区域如下:
除了对象头和实例数据外,对象在内存中的布局还有一块为对其填充,对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
综上,java对象,在内存中的布局,大致分为三块:对象头,实例数据,对其填充。
3.1.2 对象头
弄明白了java对象的内存分布,会发现,在java对象中,会发现,在java对象存储中,有个对象头,对象头中有个Mark Word, Mark Word在jvm中是markoop对象,下面是前面提到的对象头定义,其中_mark就是Mark Word。
class oopDesc {
friend class VMStructs;
private:
volatile markOop _mark;
union _metadata {
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;
}
markOop定义在hotspot源码中的markoop.hpp文件中,下图是markoop(Mark Word)的存储状态定义代码:
在32位操作系统中,用图表示为:
在markoop中定义了枚举值与之相对应:
enum { locked_value = 0,//00 轻量级锁
unlocked_value = 1,//01 无锁
monitor_value = 2,//10 重量级锁
marked_value = 3,//11 GC标记
biased_lock_pattern = 5 //101 偏向锁
};
讲到这里,就明白了为什么每个对象都能够成为锁,因为每个对象都有对象头,而锁标记是存储在对象头中的Mark Word中。
3.2 synchronized 初探
如有如下代码:
public class SynchronizedDemo2 {
public int count;
public void fun2(){
synchronized(this){
count ++;
}
}
public static void main(String[] args) {
SynchronizedDemo2 synchronizedDemo = new SynchronizedDemo2();
synchronizedDemo.fun2();
}
}
在编译生成class文件后,通过javap -v 命令查看class字节码文件如下:
如上图,在进入synchronized代码块的时候会执行monitorenter指令,在退出代码块的时候会执行monitorexit指令,编译后之所以会有两个monitorexit是因为需要在抛异常的时候释放锁。
monitotenter和monitorexit指令执行的时候具体做了什么呢?这就需要看jvm的具体实现,需要找到monitorenter和monitorexit在jvm中对应的代码一代究竟。想要找到monitorenter和monitorexit在jvm中对应的代码,就需要了解jvm的class字节码是如何执行的?
在早期的jvm中,在解释执行class指令的时候,是通过把每个class指令映射为C++代码,具体定义在jvm源码中的bytecodeInterpreter.cpp文件中,如monitorenter代码片段如下:
但是这种解释执行速度很慢,原因是每个class字节码指令对应一堆的C++代码,一堆的C++代码会翻译成一堆的CPU执行的指令,会存在很多冗余指令,都知道指令越多,CPU的执行速度肯定会越慢,在无法对编译器就行优化的前提下,怎样才能提高java字节码的执行速度呢?之所执行速度慢,就是经过了字节码指令转C++这一步操作导致大量的冗余代码,所以是否能够跳过这一步,直接将class对应为本地机器的机器码呢?是的,jvm团队就是这样做的?所以最早起的解释执行就被废止,而采用模板执行器,就是将每个class字节码对应为一段汇编代码,具体定义在jvm源码中的 templateTable.cpp文件中:
templateTable.cpp中对于指令的汇编实现,对于不同的CPU有不同的实现,如x86的在templateTable_x86.cpp以及interp_masm_x86.cpp中。汇编比较晦涩,下面的源码都看bytecodeInterpreter.cpp中的C++实现,和汇编没有本质区别。
3.3 synchronized锁的升级
在jdk1.6之前,当一个线程获得synchronized,会阻塞后面的所有其他线程,而java的线程和操作系统的线程是一一映射的,所以阻塞和唤醒线程,都需要操作系统提供支持,都需要从用户态转入内核态,需要耗费大量的CPU的时间,这种状态转换用时可能比执行用户代码的时间还要长。所以在jdk1.6之前,synchronized都是一个重量级的操作。而在jdk1.6之后,对synchronized进行的优化,引入了偏向锁和轻量级锁的概念,使得synchronized的性能得到了很大的提升。jdk1.6之后的synchronized,从偏向锁到轻量级锁,再到重量级锁,是一个锁的升级过程。下面先介绍三种锁的实现原理,再讲解升级过程。
3.3.1 偏向锁
首先要了解,为什么要引入偏向锁?
因为发现,在很多时候,对一段代码块虽然加了synchronized,但是,依然只有一个线程去访问(同一时段,不存在两个线程都要访问同步代码块的情况)。所以就引入了偏向锁。
偏向锁的偏,就是偏心,偏袒的意思,就是当一个线程获取了偏向锁后,如果在接下来的执行过程中,只要没有其他线程尝试去获取锁,则持有锁的当前线程再次进入同步代码块时不再需要进行加锁。
通过前面的讲解,我们知道了锁标记是记录在对象头中的,JVM可以通过参数 -XX:+UseBiasedLocking 启用偏向锁,在jdk1.6以后,默认是开启的。所以在通过new创建对象为对象分配内存的时候,也会判断JVM是否启用的偏向锁而对对象头的Mark Word进行初始化,具体在bytecodeInterpreter.cpp中,代码如下。
CASE(_new): {
......
......
//判断是否启用偏向锁对对象头进行初始化
if (UseBiasedLocking) {
result->set_mark(ik->prototype_header());
} else {
result->set_mark(markOopDesc::prototype());
}
......
......
}
如果启用了偏向锁,对象初始化的时候,就会初始化对象头 Mark Word为:
3.3.1.1偏向锁的获取流程
代码就在bytecodeInterpreter.cpp定义的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;
//从当前线程栈中获取一个空闲的BasicObjectLock
while (most_recent != limit ) {
if (most_recent->obj() == NULL) entry = most_recent;
else if (most_recent->obj() == lockee) break;
most_recent++;
}
//获取BasicObjectLock成功
if (entry != NULL) {
//将线程栈中的BasicObjectLock的obj指针指向锁对象
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()) {
uintptr_t thread_ident;
uintptr_t anticipated_bias_locking_value;
thread_ident = (uintptr_t)istate->thread();
anticipated_bias_locking_value =
(((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
~((uintptr_t) markOopDesc::age_mask_in_place);
//已经偏向,并且偏向的就是自己,什么都不用做
if (anticipated_bias_locking_value == 0) {
// already biased towards this thread, nothing to do
if (PrintBiasedLockingStatistics) {
//如果需要统计偏向锁重入次数,可以使用biased_lock_entry_count_addr统计
(* BiasedLocking::biased_lock_entry_count_addr())++;
}
success = true;
}
//对象的Klass的对象头不是偏向模式,则撤销偏向
else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
// try revoke bias
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(*BiasedLocking::revoked_lock_entry_count_addr())++;
}
}
//当前对象的对象头epoch不等于Klass中的epoch,则尝试重新偏向
else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
// try rebias
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将对象头中线程Id替换为自己,如果替换成功,则偏向成功
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::rebiased_lock_entry_count_addr())++;
}
//替换失败,则开始锁的升级
else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
else {
//重新偏向, 如果markOop是匿名偏向的时候会偏向当前线程成功
// try to bias towards thread in case object is anonymously biased
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);
}
markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
// debugging hint
DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
//进行CAS替换,如果替换成功,则偏向成功(只有markOop是匿名偏向的时候,才会替换成功)
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
}
//失败,则升级
else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
}
// traditional lightweight locking
//轻量级锁
if (!success) {
//设置当前的对象头为无锁状态,并且复制到Lock Record中的markoop中
markOop displaced = lockee->mark()->set_unlocked();
entry->lock()->set_displaced_header(displaced);
bool call_vm = UseHeavyMonitors;
//将对象头中的地址替换为指向Lock Record的指针,替换成功,则说明获取轻量级锁成功,则什么都不做。
//这里判断,如果是替换失败,则继续判断是否是锁重入。
if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
// Is it simple recursive case?
//这里判断是不是锁重入,判断指向Lock Record的指针指向的地址是否属于当前线程栈
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
//如果是轻量级锁的锁重入,说明前面set_displaced_header设置的是第一个Lock Record的地址,
//所以要重新将申请的Lock Record的displaced_header置为空,同样也会通过申请的displaced_header的个数来统计轻量级锁的重入次数
//栈的最高位的Lock Record的displaced_header不是空,重入锁退出锁的时候,会由低到高遍历退出,只在最后一个锁的时候使用CAS替换
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
}
}
- 从当前线程栈中找到一个空闲的Lock Record(Lock Record是BasicObjectLock对象),判断Lock Record是否空闲的依据是其obj字段 是否为null。
BasicObjectLock定义为:
class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
friend class VMStructs;
private:
BasicLock _lock; // the lock, must be double word aligned
oop _obj; // object holds the lock;
}
可以看到有两部分,BasicLock对象和Oop。oop是用来指向锁对象的,BasicLock是什么呢?BasicLock定义如下
class BasicLock VALUE_OBJ_CLASS_SPEC {
friend class VMStructs;
friend class JVMCIVMStructs;
private:
volatile markOop _displaced_header;
}
可以看到,BasicLock中就是一个markOop对象,这个就是我们前面提到的对象头,这个干什么用呢,在后面讲到轻量级锁的时候,你就会了解到。
从线程栈中申请到Lock Record后,情况是这样的:
-
为Lock Record中的obj赋值,将Lock Record中的obj指针指向锁对象。
-
判断,当前锁对象是否是偏向模式,如果是,则开始执行获取偏向锁的流程。不是,则执行获取轻量级锁的流程。
-
如果是偏向模式,则当前线程开始获取偏向锁,里面的流程逻辑比较复杂,这里简单归纳就是:
(1)判断当前线程已经偏向,并且偏向的线程是自己,则什么都不做。
(2)如果当前锁对象的Klass的对象头不是偏向模式(可能对象头已经锁升级),则尝试撤销当前对象的偏向锁。
(3)如果当前现成从来没有偏向过任何现成,则尝试通过CAS将偏向线程改为当前线程,替换成功,则偏向成功。否则执行InterpreterRuntime::monitorenter进行锁升级。如果CAS修改成功,说明当前线程T1获取当前对象偏向锁成功,情况如下:
-
这里需要提的是,如果不是偏向模式,或者在第二步中撤销了偏向锁,就进入的轻量级锁获取的逻辑。轻量级锁的获取,在后面会详细讲到。
这里需要注意的是,如果当前持有偏向锁的线程出现的锁重入,情况是如何呢?
根据上面的将的流程最终形成的情况如下,并且在锁重入过程中,并没有使用CAS。
整个获取流程如下:
说完了偏向锁的获取流程,下面看一下偏向锁的退出流程(注意这里是退出,不是撤销):
3.3.1.2偏向锁的退出流程
注意这里是退出,是指执行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();
//循环遍历线程栈中的Lock Record
while (most_recent != limit ) {
//如果Lock Record的Obj指向的是当前锁对象,说明是当前锁对象的Lock Record
if ((most_recent)->obj() == lockee) {
BasicLock* lock = most_recent->lock();
markOop header = lock->displaced_header();
//将obj设置为Null
most_recent->set_obj(NULL);
//如果不是偏向模式(即是轻量级锁)
if (!lockee->mark()->has_bias_pattern()) {
bool call_vm = UseHeavyMonitors;
// If it isn't recursive we either must swap old header or call the runtime
if (header != NULL || call_vm) {
//将对象头中的markoop替换为Lock Record中的markoop
if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
// restore object for the slow case
//如果替换失败,则还原Lock Record,并且执行锁升级的monitorexit
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();
}
上面其实是偏向锁以及轻量级锁的的退出逻辑:
- 遍历当前线程栈中的所有的Lock Record, 如果Lock Record的obj指向的是自己,则说明当前的Lock Record属于当前锁对象。
- 将Lock Record的obj设置为空。如果是偏向锁,则就不做其他操作了。
- 如果是轻量级锁,设置为空后,还需要通过CAS将Lock Record中的mark oop替换回对象头中的markoop,如果替换失败,则需要恢复Lock Record,并执行锁升级的逻辑InterpreterRuntime::monitorexit,轻量级锁后面详细说明。
这里需要注意两个点:
- 这里使用的是循环,之所以循环,就是存在锁重入的情况,需要把每个Lock Record的obj都置为空。
- 退出,仅仅是把Lock Record的obj置为空,并没有对象头中的线程ID替换掉。所以当线程T1退出的时候,情况如下:
3.3.1.3偏向锁的撤销流程
在这里,如果获取偏向锁失败了,如在上面的情况中,线程1获取了偏向锁,对象头中存放的是线程1的线程ID,此时,线程2直行到同步代码块,开始执行获取锁的流程,因为对象头中存放的是线程1的ID,所以线程2再通过CAS将对象头替换成线程2的ID的时候,肯定会替换失败,此时就会执行InterpreterRuntime::monitorenter进行偏向锁的撤销。撤销根据不同的情况,可能会重新偏向,可能会撤销成为无锁状态,也可能升级为轻量级锁。撤销流程首先执行InterpreterRuntime::monitorenter,代码如下:
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
}
这里判断是否启用了偏向锁,这个是jvm参数设置的,前面有讲过,启用了则执行fast_enter,fast_enter是偏向锁的撤销,slow_enter是轻量级锁的升级,轻量级锁在后面详细说明。fast_enter定义在synchronizer.cpp中,这里注意,传入的第三个参数attempt_rebias(尝试重新偏向为true),后面会经常用来做判断:
这里是在看偏向锁的撤销,所以是看fast_enter的实现:
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 {
//说明是安全点触发的撤销,这种撤销是jvm发起的
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);
}
这里判断了是不是在安全点触发的撤销,在安全点发起的撤销是有JVM发起的,为什么会有这样的判断呢?
首先,要撤销偏向锁,一定不能够影响程序本身的执行。有以下三种情况:
- 如果当前对象偏向锁并没有偏向任何线程,这样不管在任何时间撤销都是没有影响的。
- 如果是当前线程持有对象锁,然后当前线程撤销自己持有的对象锁的偏向,也是没有问题的。
- 如果线程1获取了偏向锁,当线程2来获取锁时,发现偏向锁被线程1持有,此时要发起偏向锁的撤销,因为锁已经被线程1持有,线程2不能立即撤销偏向锁,因为会影响线程1。所以此时撤销不能由线程2发起,就会将撤销的任务交给JVM,JVM会在安全点(安全点相关知识自行谷歌)发起偏向锁的撤销,会在下面看到这个逻辑。
根据我们的分析执行到这里,说明就是线程1获取了偏向锁,而线程2尝试执行撤销的情况,这种情况不是JVM引起的,所以继续执行BiasedLocking::revoke_and_rebias。BiasedLocking::revoke_and_rebias定义在biasedLocking.cpp中,如下:
BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");
// We can revoke the biases of anonymously-biased objects
// efficiently enough that we should not cause these revocations to
// update the heuristics because doing so may cause unwanted bulk
// revocations (which are expensive) to occur.
markOop mark = obj->mark();
//如果不允许重新偏向,则执行
if (mark->is_biased_anonymously() && !attempt_rebias) {
// We are probably trying to revoke the bias of this object due to
// an identity hash code computation. Try to revoke the bias
// without a safepoint. This is possible if we can successfully
// compare-and-exchange an unbiased header into the mark word of
// the object, meaning that no other thread has raced to acquire
// the bias of the object.
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;
}
} else if (mark->has_bias_pattern()) {
Klass* k = obj->klass();
//获取Klass的markOop
markOop prototype_header = k->prototype_header();
//如果Klass的Oop不是偏向模式则执行
if (!prototype_header->has_bias_pattern()) {
// This object has a stale bias from before the bulk revocation
// for this data type occurred. It's pointless to update the
// heuristics at this point so simply update the header with a
// CAS. If we fail this race, the object's bias has been revoked
// by another thread so we simply return and let the caller deal
// with it.
markOop biased_value = mark;
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);
assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked");
return BIAS_REVOKED;
//如果Klass markOop和锁对象的markOop的epoch不同,则执行
} else if (prototype_header->bias_epoch() != mark->bias_epoch()) {
// The epoch of this biasing has expired indicating that the
// object is effectively unbiased. Depending on whether we need
// to rebias or revoke the bias of this object we can do it
// efficiently enough with a CAS that we shouldn't update the
// heuristics. This is normally done in the assembly code but we
// can reach this point due to various points in the runtime
// needing to revoke biases.
if (attempt_rebias) {
assert(THREAD->is_Java_thread(), "");
markOop biased_value = mark;
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) {
return BIAS_REVOKED_AND_REBIASED;
}
} else {
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;
}
}
}
}
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();
//当前对象偏向当前线程,并且对象的epoch和Klass的Epoch相同,则直接开始执行撤销
if (mark->biased_locker() == THREAD &&
prototype_header->bias_epoch() == mark->bias_epoch()) {
// A thread is trying to revoke the bias of an object biased
// toward it, again likely due to an identity hash code
// computation. We can again avoid a safepoint in this case
// since we are only going to walk our own stack. There are no
// races with revocations occurring in other threads because we
// reach no safepoints in the revocation path.
// Also check the epoch because even if threads match, another thread
// can come in with a CAS to steal the bias of an object that has a
// stale epoch.
ResourceMark rm;
if (TraceBiasedLocking) {
tty->print_cr("Revoking bias by walking my own stack:");
}
BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD);
((JavaThread*) THREAD)->set_cached_monitor_info(NULL);
assert(cond == BIAS_REVOKED, "why not?");
return cond;
} else {
//如果需要撤销的不是当前线程,则需要等待线程安全点,之后Jvm在线程安全点会触发撤销程序
VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
VMThread::execute(&revoke);
return revoke.status_code();
}
}
assert((heuristics == HR_BULK_REVOKE) ||
(heuristics == HR_BULK_REBIAS), "?");
VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,
(heuristics == HR_BULK_REBIAS),
attempt_rebias);
VMThread::execute(&bulk_revoke);
return bulk_revoke.status_code();
}
参数attempt_rebias是说明是否允许尝试重新偏向,从外面传入的是true,然后,根据代码中我标明的注释,可以看出前面的代码都是不会执行的,在最后开始执行撤销,执行撤销前,先判断了如果当前锁对象偏向当前线程,则直接执行撤销。如果不是,则将通过Vm在安全点的时候执行撤销程序。这里也就是前面分析的,线程1获取了锁,而线程2尝试去撤销,此时就会将撤销交给jvm。
如果你去跟交给JVM执行的撤销逻辑,你会发现,最终调用的还是revoke_bias。只是入参会有变化,所以我们这里直接看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()) {
if (TraceBiasedLocking) {
ResourceMark rm;
tty->print_cr(" (Skipping revocation of object of type %s because it's no longer biased)",
obj->klass()->external_name());
}
return BiasedLocking::NOT_BIASED;
}
uint age = mark->age();
//偏向锁的原始markoop
markOop biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age);
//无锁的原始markoop
markOop unbiased_prototype = markOopDesc::prototype()->set_age(age);
if (TraceBiasedLocking && (Verbose || !is_bulk)) {
ResourceMark rm;
tty->print_cr("Revoking bias of object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s , prototype header " INTPTR_FORMAT " , allow rebias %d , requesting thread " INTPTR_FORMAT,
p2i((void *)obj), (intptr_t) mark, obj->klass()->external_name(), (intptr_t) obj->klass()->prototype_header(), (allow_rebias ? 1 : 0), (intptr_t) requesting_thread);
}
JavaThread* biased_thread = mark->biased_locker();
//如果当前锁对象中的线程id为空
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.
//如果不允许偏向,则设置对象头的markoop为无锁状态
if (!allow_rebias) {
obj->set_mark(unbiased_prototype);
}
if (TraceBiasedLocking && (Verbose || !is_bulk)) {
tty->print_cr(" Revoked bias of anonymously-biased object");
}
return BiasedLocking::BIAS_REVOKED;
}
// Handle case where the thread toward which the object was biased has exited
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) {
//如果允许偏向,则设置markoop为偏向锁未偏向状态
obj->set_mark(biased_prototype);
} else {
//如果不允许偏向,则设置markoop为无锁状态
obj->set_mark(unbiased_prototype);
}
if (TraceBiasedLocking && (Verbose || !is_bulk)) {
tty->print_cr(" Revoked bias of object biased toward dead thread");
}
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;
//如果线程还存活,则需要遍历线程栈中的所有Lock Record,找到当前线程对应的Lock Record,判断持有锁的线程是否已经退出
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) {
if (TraceBiasedLocking && Verbose) {
tty->print_cr(" mon_info->owner (" PTR_FORMAT ") == obj (" PTR_FORMAT ")",
p2i((void *) mon_info->owner()),
p2i((void *) obj));
}
// Assume recursive case and fix up highest lock later
markOop mark = markOopDesc::encode((BasicLock*) NULL);
highest_lock = mon_info->lock();
//设置Lock Record的markoop为NULL
highest_lock->set_displaced_header(mark);
} else {
if (TraceBiasedLocking && Verbose) {
tty->print_cr(" mon_info->owner (" PTR_FORMAT ") != obj (" PTR_FORMAT ")",
p2i((void *) mon_info->owner()),
p2i((void *) obj));
}
}
}
//如果线程栈中还存在当前对象头的Lock Record,则说明还在同步代码块中
if (highest_lock != NULL) {
// Fix up highest lock to contain displaced header and point
// object at it
//设置Lock Record的markoop为无锁
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).
//设置对象头中的指针指向Lock Record,升级为轻量级锁,这里不用设置Lock Record的obj执行对象头,因为本身已经指向了
obj->release_set_mark(markOopDesc::encode(highest_lock));
assert(!obj->mark()->has_bias_pattern(), "illegal mark state: stack lock used bias bit");
if (TraceBiasedLocking && (Verbose || !is_bulk)) {
tty->print_cr(" Revoked bias of currently-locked object");
}
//如果已经不再当前线程中了,说明当前线程已经退出了锁
} else {
if (TraceBiasedLocking && (Verbose || !is_bulk)) {
tty->print_cr(" Revoked bias of currently-unlocked object");
}
if (allow_rebias) {
//允许重新偏向,则设置线程的状态为偏向锁
obj->set_mark(biased_prototype);
} else {
// Store the unlocked value into the object's header.
//允不许重新偏向,则设置线程的状态为无锁锁
obj->set_mark(unbiased_prototype);
}
}
return BiasedLocking::BIAS_REVOKED;
}
上面的撤销逻辑看似比较长,其实核心逻辑并不复杂:
- 如果当前对象锁已经不是偏向模式了,就不用执行撤销。
- 如果当前锁对象偏向的线程Id为NULL,也就是没有偏向任何线程,就根据参数allow_rebias判断是否允许重新偏向,不允许就设置锁状态为无锁,相当于撤销偏向。
- 判断当前锁对象中偏向的线程是否存活,如果持有偏向锁的线程已经死掉了,那如果允许重新偏向就设置对象头锁状态为偏向锁的初始状态,不允许就设置为无锁状态。
- 如果线程还存活,就开始执行真正的撤销了:
这里回忆一下前面偏向锁的获取和退出流程:偏向锁的获取,就是在当前线程的线程栈中申请一块Lock Record,然后将Lock Record的obj指向锁对象,并且在对象头中存储当前线程的线程Id。而偏向锁的退出,仅仅将Lock Record中的obj值置为空,其他的什么都没有做。
如果持有偏向锁的线程依旧存活,这里就有两种情况
(1)持有偏向锁的线程还没有退出同步代码块
(2)第二是持有偏向锁的线程退出了同步代码块。
而判断是否已经退出,判断依据就是线程栈中是否还有指向锁对象的Lock Record,这以上面的代码中,首先就是遍历线程栈,判断持有锁的线程是否退出了。
遍历结束后,如果highest_lock不等于空,说还没有退出,如果等于NULL说明已经退出了。
如果还在代码块中没有退出,就需要升级锁为轻量级锁,升级为轻量级锁业很简单,先将Lock Record的dispatcher_header设置为无锁的markoop,在把锁对象头替换成指向LockRecord的指针。后面看完轻量级锁,再回过头看这里的升级过程,就会明白了。
如果持有偏向锁的线程已经退出了,则判断是否允许重新偏西,如果允许重新偏向,就设置锁对象的对象头为偏向锁的初始状态。否则设置为无锁状态,即撤销偏向锁。
偏向锁撤销流程如下,这里只看最终revoke_bias的流程:
3.3.1.4 偏向锁与HashCode
偏向锁到这里就讲完了,但是不知道大家有没有疑问,如果JVM配置允许使用偏向锁,那原对象头中存储HashCode的部分,就要用来存储偏向的线程ID,那原对象的HashCode被存储到哪儿去了?直接覆盖肯定是不合理的。
答案是,如果一旦对象生成了HashCode,就不能在使用对象的偏向锁了。为什么这样说,可以直接看HashCode的源码,hashCode是本地native方法,实现最终调用是synchronizer.cpp中的ObjectSynchronizer::FastHashCode,具体如下:
intptr_t ObjectSynchronizer::FastHashCode(Thread * Self, oop obj) {
if (UseBiasedLocking) {
// NOTE: many places throughout the JVM do not expect a safepoint
// to be taken here, in particular most operations on perm gen
// objects. However, we only ever bias Java instances and all of
// the call sites of identity_hash that might revoke biases have
// been checked to make sure they can handle a safepoint. The
// added check of the bias pattern is to avoid useless calls to
// thread-local storage.
if (obj->mark()->has_bias_pattern()) {
// Handle for oop obj in case of STW safepoint
Handle hobj(Self, obj);
// Relaxing assertion for bug 6320749.
assert(Universe::verify_in_progress() ||
!SafepointSynchronize::is_at_safepoint(),
"biases should not be seen by VM thread here");
//首先就判断了JVM是否启用了偏向锁(这个由参数配置),然后判断当前是否是偏向模式(即偏向还没有被撤销),
//如果都是,则撤销偏向模式
BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
obj = hobj();
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
}
// hashCode() is a heap mutator ...
// Relaxing assertion for bug 6320749.
assert(Universe::verify_in_progress() || DumpSharedSpaces ||
!SafepointSynchronize::is_at_safepoint(), "invariant");
assert(Universe::verify_in_progress() || DumpSharedSpaces ||
Self->is_Java_thread() , "invariant");
assert(Universe::verify_in_progress() || DumpSharedSpaces ||
((JavaThread *)Self)->thread_state() != _thread_blocked, "invariant");
ObjectMonitor* monitor = NULL;
markOop temp, test;
intptr_t hash;
markOop mark = ReadStableMark(obj);
// object should remain ineligible for biased locking
assert(!mark->has_bias_pattern(), "invariant");
//无锁状态
if (mark->is_neutral()) {
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
// use (machine word version) atomic operation to install the hash
test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
if (test == mark) {
return hash;
}
// If atomic operation failed, we must inflate the header
// into heavy weight monitor. We could add more code here
// for fast path, but it does not worth the complexity.
//这种是重量级锁的情况下,hash存储在monitor中
} else if (mark->has_monitor()) {
monitor = mark->monitor();
temp = monitor->header();
assert(temp->is_neutral(), "invariant");
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())) {
temp = mark->displaced_mark_helper(); // this is a lightweight monitor owned
assert(temp->is_neutral(), "invariant");
hash = temp->hash(); // by current thread, check if the displaced
if (hash) { // header contains hash code
return hash;
}
// WARNING:
// The displaced header is strictly immutable.
// It can NOT be changed in ANY cases. So we have
// to inflate the header into heavyweight monitor
// even the current thread owns the lock. The reason
// is the BasicLock (stack slot) will be asynchronously
// read by other threads during the inflate() function.
// Any change to stack may not propagate to other threads
// correctly.
}
// Inflate the monitor to set hash code
// 到这儿,说明没有获取到hashCode,首先直接将锁膨胀为轻量级锁,然后获取hashcode并且设置hsahcode
monitor = ObjectSynchronizer::inflate(Self, obj);
// Load displaced header and check it has hash code
mark = monitor->header();
assert(mark->is_neutral(), "invariant");
hash = mark->hash();
if (hash == 0) {
hash = get_next_hash(Self, obj);
temp = mark->copy_set_hash(hash); // merge hash code into header
assert(temp->is_neutral(), "invariant");
test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
if (test != mark) {
// The only update to the header in the monitor (outside GC)
// is install the hash code. If someone add new usage of
// displaced header, please update this code
hash = test->hash();
assert(test->is_neutral(), "invariant");
assert(hash != 0, "Trivial unexpected object/monitor header usage.");
}
}
// We finally get the hash
return hash;
}
从实现中可以看出,执行hashCode,第一件要做的事情就是:判断了JVM是否启用了偏向锁(这个由参数配置),然后判断当前是否是偏向模式(即偏向还没有被撤销),如果都是,则撤销偏向模式。
从上面的代码中也可以看出,对象的HashCode并不是在对象的创建的时候就生成了,而是只有在调用HashCode的时候才生成,并且,一旦生成了HashCode,就会存储在对象头中,不会再次生成,至于后面的轻量级锁,重量级锁,虽然也会覆盖对象头,但是都会把对象头中的内容copy一份存储在特定的地方,后面 可以详细看到。
请注意,这里讨论的hash code都只针对调用了JVM的hashCode,是未被重写的 hashCode() 。如果用户重写了hashCode()方法,则不影响。
3.3.2 轻量级锁
为什么要引入轻量级锁?
在大部分情况下,对一段代码块加了synchronized,但多个线程之间都是交替去访问同步代码块,而非同一个线程长时间占用锁。
3.3.2.1 轻量级锁的获取过程
轻量级锁的获取代码和偏向锁的代码再一起,在对象不是偏向模式情况下,会执行获取轻量级锁的逻辑,具体如下,为了方便起见,我删除了偏向锁的逻辑。
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;
//获取一个空闲的BasicObjectLock
while (most_recent != limit ) {
if (most_recent->obj() == NULL) entry = most_recent;
else if (most_recent->obj() == lockee) break;
most_recent++;
}
//获取BasicObjectLock成功
if (entry != NULL) {
//将obj指针指向锁对象
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) {
//设置当前的对象头为无锁状态,并且复制到Lock Record中的markoop中
markOop displaced = lockee->mark()->set_unlocked();
entry->lock()->set_displaced_header(displaced);
bool call_vm = UseHeavyMonitors;
//将对象头中的地址替换为执行Lock Record的指针,替换成功,则说明获取轻量级锁成功
if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
// Is it simple recursive case?
//这里判断是不是锁重入,
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
//如果是轻量级锁的锁重入,说明前面set_displaced_header设置的是第一个Lock Record的地址,
//所以要重新将申请的Lock Record的displaced_header置为空
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
}
}
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
}
如上面代码可以看到,偏向锁的获取流程如下:
- 首先在当前线程栈中申请Lock Record,将Lock Record中的obj指向锁对象。这个和偏向锁都要做的事情。
- 取出当前锁对象的markoop,并设置其锁状态为无锁,并将其存储在Lock Record中的displaced header中。这里之所以设置为无锁,因为当轻量级锁释放退出的时候,需要将displaced header替换回对象头中,轻量级锁释放退出后,对象头的状态就是无锁状态,所以这里直接设置成无锁。
- 通过CAS将锁对象对象头替换成指向Lock Record的指针,替换成功后,情况如下:
替换成功后,说明当前线程获取轻量级锁成功了。对象头的状态如下:
4. 如果替换失败,先判断一下是否是轻量级锁重入,如果是,说明之前已经申请过Lock Record了,就需要把新申请的Lock Record的displaced header置为空,因为一旦是重入,第二个Lock Record的displaced header存储的会是第一个Lock Record的地址。并且,存储锁对象的markoop也是为了避免覆盖保存一份,不需要保存多份。所以是重入,只是重新申请了新的Lock Record并且将obj指向对象头。用申请的lock
Record的个数来记录重入的次数。
5. 如果替换失败了,并且不是锁重入,就执行InterpreterRuntime::monitorenter进行锁升级。
轻量级锁的获取流程如下:
3.3.2.2 轻量级锁的释放过程
这里的释放,即退出synchronized。
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();
//循环遍历线程栈中的Lock Record
while (most_recent != limit ) {
//如果Lock Record的Obj指向的是当前锁对象,说明是当前锁对象的Lock Record
if ((most_recent)->obj() == lockee) {
BasicLock* lock = most_recent->lock();
markOop header = lock->displaced_header();
//将obj设置为Null
most_recent->set_obj(NULL);
//如果不是偏向模式(即是轻量级锁)
if (!lockee->mark()->has_bias_pattern()) {
bool call_vm = UseHeavyMonitors;
// If it isn't recursive we either must swap old header or call the runtime
//这里退出锁的逻辑和获取锁是对应的,前面发现是重入,已经将displaced_header置为空了,所以这里就什么都不需要做了。
if (header != NULL || call_vm) {
//将对象头中的markoop替换为Lock Record中的markoop
if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
// restore object for the slow case
//如果替换失败,则还原Lock Record,并且执行锁升级的monitorexit
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();
}
1、循环遍历当前线程的线程栈,找到指向当前锁对象的Lock Record.
2、将Lock Record的obj设置为空,也就是不再让Lock Record指向锁对象。(这个动作偏向锁也会有)
3、判断如果是轻量级锁,然后判断如果Lock Record的Displaced header不为空,则通过CAS将Displaced header中的markoop替换回对象头中。前面讲轻量级锁获取的时候也有提到过,如果是轻量级锁重入,则Lock Record的Displaced header设置为空,这里退出的时候,会判断如果不为空则替换。
4、如果替换成功,则释放锁成功。如果替换失败,则说明当前锁被其他线程抢占过。所以要执行InterpreterRuntime::monitorexit的退出逻辑,monitorexit中,主要做的是轻量级锁的退出和锁膨胀为重量级锁,具体如下:
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());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
if (elem == NULL || h_obj()->is_unlocked()) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
// Free entry. This must be done here, since a pending exception might be installed on
// exit. If it is not cleared, the exception handling code will try to unlock the monitor again.
elem->set_obj(NULL);
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
上面首先执行了ObjectSynchronizer::slow_exit,ObjectSynchronizer::slow_exit中调用的是fast_exit, 然后将Lock Record的obj置为空。fast_exit逻辑如下:
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");
// if displaced header is null, the previous enter is recursive enter, no-op
markOop dhw = lock->displaced_header();
markOop mark;
//这里判断,Lock Record的displaced_header是否为空,如果是,当前的Lock Record是重入中的一个。
//如果没有持有轻量级锁,那就没必要执行膨胀了,直接返回。中间assert了一下是否已经是重量级锁,别的什么也没做。
if (dhw == NULL) {
// Recursive stack-lock.
// Diagnostics -- Could be: stack-locked, inflating, inflated.
mark = object->mark();
assert(!mark->is_neutral(), "invariant");
if (mark->has_locker() && mark != markOopDesc::INFLATING()) {
assert(THREAD->is_lock_owned((address)mark->locker()), "invariant");
}
if (mark->has_monitor()) {
ObjectMonitor * m = mark->monitor();
assert(((oop)(m->object()))->mark() == mark, "invariant");
assert(m->is_entered(THREAD), "invariant");
}
return;
}
//执行到这儿,说明Lock Record的displaced_header不为空,说明可能是轻量级锁,也可能已经膨胀为了重量级锁。
mark = object->mark();
// If the object is stack-locked by the current thread, try to
// swing the displaced header from the box back to the mark.
//尝试将其强转为markOop,成功,则说明还是轻量级锁,则尝试用CAS将Lock Record的isplaced_header替换回对象头。
//则尝试释放轻量级锁,即通过CAS将displaced header替换回对象头中,替换成功则说明轻量级锁释放成功。
if (mark == (markOop) lock) {
assert(dhw->is_neutral(), "invariant");
if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
TEVENT(fast_exit: release stacklock);
return;
}
}
//开始执行锁的膨胀升级为重量级锁,并且执行exit
ObjectSynchronizer::inflate(THREAD, object)->exit(true, THREAD);
}
1、这里判断,Lock Record的displaced_header是否为空,如果是,则说明已经没有持有当前轻量级级锁了。如果没有持有轻量级锁,那就没必要执行膨胀了,直接返回。中间assert了一下是否已经是重量级锁,别的什么也没做。
2、如果Lock Record的displaced_header不为空, 判断如果当前对象头还是轻量级锁,并且指向的是当前Lock Record,则尝试释放轻量级锁,即通过CAS将displaced header替换回对象头中,替换成功则说明轻量级锁释放成功。
3、如果前面的判断或者是替换都失败了,就开始执行ObjectSynchronizer::inflate进行锁膨胀为重量级锁。膨胀成功后ObjectSynchronizer::inflate的返回值为ObjectMonitor对象,ObjectMonitor就是用于实现重量级锁的。然后调用ObjectMonitor的exit方法进行锁的释放。
轻量级锁的锁的退出流程:
3.2.2.3 轻量级锁的膨胀
前面讲解偏向锁,存在获取,退出,撤销重新偏向三种情况,其中在撤销的过程,根据不同的情况,会升级为轻量级锁。而轻量级锁,除了获取,释放,同样存在膨胀为重量级锁的过程,这里我们就分析一下轻量级锁膨胀为重量级锁的过程。
轻量级锁膨胀为重量级锁,要从轻量级锁CAS替换线程栈和对象头和Lock Record的markoop说起,获取和释放轻量级锁的时候都会替换,获取和退出替换失败最终执行膨胀的逻辑其实是一样的,都是ObjectSynchronizer::inflate。这里跟一下进入时的膨胀逻辑。
首先是执行InterpreterRuntime::monitorenter,定义在interpreterRuntime.cpp中:
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
这段代码,偏向锁中也看到过,不过这里是轻量级锁,所以这里调用的是ObjectSynchronizer::slow_enter, slow_enter的实现如下:
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
//无锁状态
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);
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT(slow_enter: release stacklock);
return;
}
// Fall through to inflate() ...
// 轻量级锁重入
} else if (mark->has_locker() &&
THREAD->is_lock_owned((address)mark->locker())) {
assert(lock != mark->locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
lock->set_displaced_header(NULL);
return;
}
// The object header will never be displaced to this lock,
// so it does not matter what the value is, except that it
// must be non-zero to avoid looking like a re-entrant lock,
// and must not look locked either.
//开始升级为重量级锁
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
这里逻辑很简单,首先又进行了一系列的判断是否是无锁,是否是轻量级锁重入等,都不是,就开始ObjectSynchronizer::inflate进行膨胀。
膨胀之前,将线程栈的Lock Record的displaced_header状态设置为unused_mark,标志位为11。注释的意思为:对象头永远不会被移到这个锁中,所以值是什么并不重要,除非它必须是非零的,以避免看起来像是重入者锁,而且也不能看起来是锁定的。大概可能就是说,已经升级为重量级锁了,这个displaced_header也不会再被用到,所以里面放什么都无所谓了,但是放的值不能让他看起来像是重量级锁。
膨胀成功后,返回值为ObjectMonitor,然后调用ObjectMonitor的enter方法获取锁。
所以膨胀的逻辑就是ObjectSynchronizer::inflate的实现,具体代码如下:
ObjectMonitor * NOINLINE ObjectSynchronizer::inflate(Thread * Self,
oop object) {
// Inflate mutates the heap ...
// Relaxing assertion for bug 6320749.
assert(Universe::verify_in_progress() ||
!SafepointSynchronize::is_at_safepoint(), "invariant");
for (;;) {
//获取锁对象的markoop
const markOop mark = object->mark();
assert(!mark->has_bias_pattern(), "invariant");
// The mark can be in one of the following states:
// * Inflated - just return
// * Stack-locked - coerce it to inflated
// * INFLATING - busy wait for conversion to complete
// * Neutral - aggressively inflate the object.
// * BIASED - Illegal. We should never see this
// CASE: inflated
//判断是否有Objectmonitor,如果有,说明已经膨胀过了,直接返回
if (mark->has_monitor()) {
ObjectMonitor * inf = mark->monitor();
assert(inf->header()->is_neutral(), "invariant");
assert(inf->object() == object, "invariant");
assert(ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
return inf;
}
// CASE: inflation in progress - inflating over a stack-lock.
// Some other thread is converting from stack-locked to inflated.
// Only that thread can complete inflation -- other threads must wait.
// The INFLATING value is transient.
// Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
// We could always eliminate polling by parking the thread on some auxiliary list.
//如果有其他线程正在执行膨胀,则当前线程不再去执行膨胀逻辑
if (mark == markOopDesc::INFLATING()) {
TEVENT(Inflate: spin while INFLATING);
ReadStableMark(object);
continue;
}
// CASE: stack-locked
// Could be stack-locked either by this thread or by some other thread.
//
// Note that we allocate the objectmonitor speculatively, _before_ attempting
// to install INFLATING into the mark word. We originally installed INFLATING,
// allocated the objectmonitor, and then finally STed the address of the
// objectmonitor into the mark. This was correct, but artificially lengthened
// the interval in which INFLATED appeared in the mark, thus increasing
// the odds of inflation contention.
//
// We now use per-thread private objectmonitor free lists.
// These list are reprovisioned from the global free list outside the
// critical INFLATING...ST interval. A thread can transfer
// multiple objectmonitors en-mass from the global free list to its local free list.
// This reduces coherency traffic and lock contention on the global free list.
// Using such local free lists, it doesn't matter if the omAlloc() call appears
// before or after the CAS(INFLATING) operation.
// See the comments in omAlloc().
//判断目前对象头的状态是否是轻量级锁的状态,如果是则执行
if (mark->has_locker()) {
//创建一个ObjectMonitor
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->_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
}
// We've successfully installed INFLATING (0) into the mark-word.
// This is the only case where 0 will appear in a mark-word.
// Only the singular thread that successfully swings the mark-word
// to 0 can perform (or more precisely, complete) inflation.
//
// Why do we CAS a 0 into the mark-word instead of just CASing the
// mark-word from the stack-locked value directly to the new inflated state?
// Consider what happens when a thread unlocks a stack-locked object.
// It attempts to use CAS to swing the displaced header value from the
// on-stack basiclock back into the object header. Recall also that the
// header value (hashcode, etc) can reside in (a) the object header, or
// (b) a displaced header associated with the stack-lock, or (c) a displaced
// header in an objectMonitor. The inflate() routine must copy the header
// value from the basiclock on the owner's stack to the objectMonitor, all
// the while preserving the hashCode stability invariants. If the owner
// decides to release the lock while the value is 0, the unlock will fail
// and control will eventually pass from slow_exit() to inflate. The owner
// will then spin, waiting for the 0 value to disappear. Put another way,
// the 0 causes the owner to stall if the owner happens to try to
// drop the lock (restoring the header from the basiclock to the object)
// while inflation is in-progress. This protocol avoids races that might
// would otherwise permit hashCode values to change or "flicker" for an object.
// Critically, while object->mark is 0 mark->displaced_mark_helper() is stable.
// 0 serves as a "BUSY" inflate-in-progress indicator.
// fetch the displaced mark from the owner's stack.
// The owner can't die or unwind past the lock while our INFLATING
// object is in the mark. Furthermore the owner can't complete
// an unlock on the object, either.
markOop dmw = mark->displaced_mark_helper();
assert(dmw->is_neutral(), "invariant");
// Setup monitor fields to proper values -- prepare the monitor
m->set_header(dmw);
// Optimization: if the mark->locker stack address is associated
// with this thread we could simply set m->_owner = Self.
// Note that a thread can inflate an object
// that it has stack-locked -- as might happen in wait() -- directly
// with CAS. That is, we can avoid the xchg-NULL .... ST idiom.
m->set_owner(mark->locker());
m->set_object(object);
// TODO-FIXME: assert BasicLock->dhw != 0.
// Must preserve store ordering. The monitor state must
// be stable at the time of publishing the monitor address.
guarantee(object->mark() == markOopDesc::INFLATING(), "invariant");
//设置对象头指向当前的objectmonitor
object->release_set_mark(markOopDesc::encode(m));
// Hopefully the performance counters are allocated on distinct cache lines
// to avoid false sharing on MP systems ...
OM_PERFDATA_OP(Inflations, inc());
TEVENT(Inflate: overwrite stacklock);
if (log_is_enabled(Debug, monitorinflation)) {
if (object->is_instance()) {
ResourceMark rm;
log_debug(monitorinflation)("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
p2i(object), p2i(object->mark()),
object->klass()->external_name());
}
}
return m;
}
// CASE: neutral
// TODO-FIXME: for entry we currently inflate and then try to CAS _owner.
// If we know we're inflating for entry it's better to inflate by swinging a
// pre-locked objectMonitor pointer into the object header. A successful
// CAS inflates the object *and* confers ownership to the inflating thread.
// In the current implementation we use a 2-step mechanism where we CAS()
// to inflate and then CAS() again to try to swing _owner from NULL to Self.
// An inflateTry() method that we could call from fast_enter() and slow_enter()
// would be useful.
assert(mark->is_neutral(), "invariant");
//创建ObjectMonitor
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->_recursions = 0;
m->_Responsible = NULL;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit; // consider: keep metastats by type/class
//把对象头替换为执行objectMonitor的指针
if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
m->set_object(NULL);
m->set_owner(NULL);
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 ...
OM_PERFDATA_OP(Inflations, inc());
TEVENT(Inflate: overwrite neutral);
if (log_is_enabled(Debug, monitorinflation)) {
if (object->is_instance()) {
ResourceMark rm;
log_debug(monitorinflation)("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
p2i(object), p2i(object->mark()),
object->klass()->external_name());
}
}
return m;
}
}
看似比较长,逻辑其实很简单。
1、判断是否有Objectmonitor,如果已经有了,说明已经是重量级锁了,直接返回Objectmonitor。
2、判断是否有其他线程正在执行锁的膨胀,如果是,则continue重新执行膨胀判断逻辑,也就是继续第一步的判断。
3、如果没有Objectmonitor,则进行膨胀。膨胀过程也就是创建一个ObjectMonitor,并且进行初始化,然后将锁对象的的markoop指向Objectmonitor,然后返回。
重量级锁到这里,就大致讲完了,重量级锁需要注意的地方是,重量级锁重入,也会通过CAS进行替换,替换失败,则判断是否是重入。轻量级锁释放的时候,也需要通过CAS替换。
3.3.3 重量级锁
前面也看到了,重量级锁是通过ObjectMonitor实现的,升级为重量级锁后,锁对象会指向objectmonitor。objectmonitor其实就是一个对象,其中定义了很多用于保存锁相关信息的字段。具体如下:
private:
friend class ObjectSynchronizer;
friend class ObjectWaiter;
friend class VMStructs;
volatile markOop _header; // displaced object header word - mark ,保存锁对象的对象头markoop
void* volatile _object; // backward object pointer - strong root,指向锁对象的指针
public:
ObjectMonitor * FreeNext; // Free list linkage
private:
DEFINE_PAD_MINUS_SIZE(0, DEFAULT_CACHE_LINE_SIZE,
sizeof(volatile markOop) + sizeof(void * volatile) +
sizeof(ObjectMonitor *));
protected: // protected for JvmtiRawMonitor
void * volatile _owner; // pointer to owning thread OR BasicLock,指向拥有当前锁的线程
volatile jlong _previous_owner_tid; // thread id of the previous owner of the monitor
volatile intptr_t _recursions; // recursion count, 0 for first entry,线程锁重入次数
ObjectWaiter * volatile _EntryList; // Threads blocked on entry or reentry.,被阻塞的线程重新进入时,会将其放在当前队列中。其实这个队列是被wait()方法阻塞的线程,当调用notify/notifyAll时,会将线程放在这个队列中。
// The list is actually composed of WaitNodes,
// acting as proxies for Threads.
private:
ObjectWaiter * volatile _cxq; // LL of recently-arrived threads blocked on entry. 当对象锁已经被一个线程持有,其他所有的线程在尝试获取锁的时候,如果没有获取到,将其挂起后都会被放在这个队列上。
Thread * volatile _succ; // Heir presumptive thread - used for futile wakeup throttling
Thread * volatile _Responsible;
volatile int _Spinner; // for exit->spinner handoff optimization,Spinner相关的都是和自旋锁优化相关
volatile int _SpinFreq; // Spin 1-out-of-N attempts: success rate
volatile int _SpinClock;
volatile intptr_t _SpinState; // MCS/CLH list of spinners
volatile int _SpinDuration;
volatile jint _count; // reference count to prevent reclamation/deflation
// at stop-the-world time. See deflate_idle_monitors().
// _count is approximately |_WaitSet| + |_EntryList|
protected:
ObjectWaiter * volatile _WaitSet; // LL of threads wait()ing on the monitor,调用wait()方法阻塞的线程,都会放在当前队列中
volatile jint _waiters; // number of waiting threads
private:
volatile int _WaitSetLock; // protects Wait Queue - simple spinlock
我们这里着重注意一下以下几个字段:
volatile markOop _header; // displaced object header word - mark ,保存锁对象的对象头markoop
void* volatile _object; // backward object pointer - strong root,指向锁对象的指针
void * volatile _owner; // pointer to owning thread OR BasicLock,指向拥有当前锁的线程
volatile intptr_t _recursions; // recursion count, 0 for first entry,线程锁重入次数
ObjectWaiter * volatile _EntryList; // Threads blocked on entry or reentry.,被阻塞的线程重新进入时,会将其放在当前队列中。其实这个队列是被wait()方法阻塞的线程,当调用notify/notifyAll时,会将准备唤醒的线程放在这个队列中。
ObjectWaiter * volatile _cxq; // LL of recently-arrived threads blocked on entry. 当对象锁已经被一个线程持有,其他所有的线程在尝试获取锁的时候,如果没有获取到,将其挂起后都会被放在这个队列上。
ObjectWaiter * volatile _WaitSet; // LL of threads wait()ing on the monitor,调用wait()方法阻塞的线程,都会放在当前队列中
看了objectmonitor定义即各个字段的作用后,接下来分析重量级锁的获取以及释放的流程。
3.3.3.1 重量级锁的获取
前面看到,如果轻量级锁成功膨胀为了重量级锁,则执行膨胀objectmonitor的enter方法获取重量级锁,所以重量级锁的获取,是ObjectMonitor::enter方法,定义在objectMonitor.cpp中。
void NOINLINE ObjectMonitor::enter(TRAPS) {
// The following code is ordered to check the most common cases first
// and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
Thread * const Self = THREAD;
//通过CAS将objectmonitor的owner替换成当前线程,如果owner则替换成功。
void * cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL);
if (cur == NULL) {
//说明替换成功了,则已经获取了锁,直接返回即可
// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
assert(_recursions == 0, "invariant");
assert(_owner == Self, "invariant");
return;
}
//执行到这里,说明替换失败了,则判断owner是不是自己,如果是自己,则说明是锁重入,直接将重入次数++,返回。
if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions++;
return;
}
//is_lock_owned判断的是,owner指向的地址是否是当前线程栈上的某一地址,如果是,则直接将owner执行当前线程,然后返回
if (Self->is_lock_owned ((address)cur)) {
assert(_recursions == 0, "internal state error");
_recursions = 1;
// Commute owner from a thread-specific on-stack BasicLockObject address to
// a full-fledged "Thread *".
_owner = Self;
return;
}
// We've encountered genuine contention.
assert(Self->_Stalled == 0, "invariant");
Self->_Stalled = intptr_t(this);
// Try one round of spinning *before* enqueueing Self
// and before going through the awkward and expensive state
// transitions. The following spin is strictly optional ...
// Note that if we acquire the monitor from an initial spin
// we forgo posting JVMTI events and firing DTRACE probes.
//执行到这里,说当前锁已经被其他线程占有了,
//但是,在执行昂贵的线程挂起操作之前,先初始化一个自旋监视器,尝试自旋一下。
//在自旋的过程中会不断的尝试去获取锁。
if (Knob_SpinEarly && TrySpin (Self) > 0) {
assert(_owner == Self, "invariant");
assert(_recursions == 0, "invariant");
assert(((oop)(object()))->mark() == markOopDesc::encode(this), "invariant");
Self->_Stalled = 0;
return;
}
assert(_owner != Self, "invariant");
assert(_succ != Self, "invariant");
assert(Self->is_Java_thread(), "invariant");
JavaThread * jt = (JavaThread *) Self;
assert(!SafepointSynchronize::is_at_safepoint(), "invariant");
assert(jt->thread_state() != _thread_blocked, "invariant");
assert(this->object() != NULL, "invariant");
assert(_count >= 0, "invariant");
// Prevent deflation at STW-time. See deflate_idle_monitors() and is_busy().
// Ensure the object-monitor relationship remains stable while there's contention.
Atomic::inc(&_count);
EventJavaMonitorEnter event;
{ // Change java thread status to indicate blocked on monitor enter.
//更改java线程的状态为进入监视器时被阻塞,也就是java线程block状态
JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);
DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
if (JvmtiExport::should_post_monitor_contended_enter()) {
JvmtiExport::post_monitor_contended_enter(jt, this);
// The current thread does not yet own the monitor and does not
// yet appear on any queues that would get it made the successor.
// This means that the JVMTI_EVENT_MONITOR_CONTENDED_ENTER event
// handler cannot accidentally consume an unpark() meant for the
// ParkEvent associated with this ObjectMonitor.
}
OSThreadContendState osts(Self->osthread());
ThreadBlockInVM tbivm(jt);
//设置线程当前阻塞在的monitor为在当前的monitor。
Self->set_current_pending_monitor(this);
// TODO-FIXME: change the following for(;;) loop to straight-line code.
for (;;) {
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition()
// or java_suspend_self()
//EnterI是线程挂起的真正逻辑
EnterI(THREAD);
if (!ExitSuspendEquivalent(jt)) break;
// We have acquired the contended monitor, but while we were
// waiting another thread suspended us. We don't want to enter
// the monitor while suspended because that would surprise the
// thread that suspended us.
//
_recursions = 0;
_succ = NULL;
exit(false, Self);
jt->java_suspend_self();
}
Self->set_current_pending_monitor(NULL);
// We cleared the pending monitor info since we've just gotten past
// the enter-check-for-suspend dance and we now own the monitor free
// and clear, i.e., it is no longer pending. The ThreadBlockInVM
// destructor can go to a safepoint at the end of this block. If we
// do a thread dump during that safepoint, then this thread will show
// as having "-locked" the monitor, but the OS and java.lang.Thread
// states will still report that the thread is blocked trying to
// acquire it.
}
......
......
}
流程看似较长,核心逻辑不难理解:
-
通过CAS尝试将ObjectMonitor的owner替换指向当前线程。如果有owner == null情况下才会成功。
-
替换成功,则说明换成功,就可以返回了,如果替换失败,则判断owner是不是等于自己,如果是,那就是锁重入了,只需要将重入次数++,然后返回。
-
如果1,2步骤都失败了,说明当前锁已经被其他线程获取了,当前线程会进行自旋,不断的自旋尝试获取锁,这里简单的看一下自旋和自适应自旋的概念:
前面提到过,线程的阻塞需要挂起线程和恢复线程,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大的压力。同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。这种时候,我们就可以让后面请求锁的那个线程“稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。
自旋锁在JDK 1.4.2中就已经引入,只不过默认是关闭的,可以使用-XX:+UseSpinning参数来开启,在JDK 1.6中就已经改为默认开启了。自旋等待不能代替阻塞,因为自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性能上的浪费。因此,自旋等待的时间必须要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程了。自旋次数的默认值是10次,用户可以使用参数-XX:PreBlockSpin来更改。
在JDK 1.6中引入了自适应的自旋锁。自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如100个循环。另外,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虚拟机就会变得越来越“聪明”了。 -
如果自旋获取锁成功了,则返回,如果没有,就要开始进行线程的起操作了。
-
挂起前,设置当前线程的状态为阻塞状态,即Block,然后设置当前线程挂起在了当前对象上。然后执行EnterI(THREAD);开始真正的挂起操作。EnterI(THREAD);定义如下:
void NOINLINE ObjectMonitor::EnterI(TRAPS) {
Thread * const Self = THREAD;
assert(Self->is_Java_thread(), "invariant");
assert(((JavaThread *) Self)->thread_state() == _thread_blocked, "invariant");
// Try the lock - TATAS
if (TryLock (Self) > 0) {
assert(_succ != Self, "invariant");
assert(_owner == Self, "invariant");
assert(_Responsible != Self, "invariant");
return;
}
DeferredInitialize();
// We try one round of spinning *before* enqueueing Self.
//
// If the _owner is ready but OFFPROC we could use a YieldTo()
// operation to donate the remainder of this thread's quantum
// to the owner. This has subtle but beneficial affinity
// effects.
if (TrySpin (Self) > 0) {
assert(_owner == Self, "invariant");
assert(_succ != Self, "invariant");
assert(_Responsible != Self, "invariant");
return;
}
// The Spin failed -- Enqueue and park the thread ...
assert(_succ != Self, "invariant");
assert(_owner != Self, "invariant");
assert(_Responsible != Self, "invariant");
// Enqueue "Self" on ObjectMonitor's _cxq.
//
// Node acts as a proxy for Self.
// As an aside, if were to ever rewrite the synchronization code mostly
// in Java, WaitNodes, ObjectMonitors, and Events would become 1st-class
// Java objects. This would avoid awkward lifecycle and liveness issues,
// as well as eliminate a subset of ABA issues.
// TODO: eliminate ObjectWaiter and enqueue either Threads or Events.
ObjectWaiter node(Self);
Self->_ParkEvent->reset();
node._prev = (ObjectWaiter *) 0xBAD;
node.TState = ObjectWaiter::TS_CXQ;
// Push "Self" onto the front of the _cxq.
// Once on cxq/EntryList, Self stays on-queue until it acquires the lock.
// Note that spinning tends to reduce the rate at which threads
// enqueue and dequeue on EntryList|cxq.
ObjectWaiter * nxt;
for (;;) {
node._next = nxt = _cxq;
//尝试将当前线程放在cxq队列的头部,如果放成功了则跳出循环
if (Atomic::cmpxchg_ptr(&node, &_cxq, nxt) == nxt) break;
// Interference - the CAS failed because _cxq changed. Just retry.
// As an optional optimization we retry the lock.
//放失败了,则说明cxq对象被改变了,则尝试获取锁。
//获取成功则直接返回,获取失败,在继续尝试将线程放在Cxq队列的头部
if (TryLock (Self) > 0) {
assert(_succ != Self, "invariant");
assert(_owner == Self, "invariant");
assert(_Responsible != Self, "invariant");
return;
}
}
// Check for cxq|EntryList edge transition to non-null. This indicates
// the onset of contention. While contention persists exiting threads
// will use a ST:MEMBAR:LD 1-1 exit protocol. When contention abates exit
// operations revert to the faster 1-0 mode. This enter operation may interleave
// (race) a concurrent 1-0 exit operation, resulting in stranding, so we
// arrange for one of the contending thread to use a timed park() operations
// to detect and recover from the race. (Stranding is form of progress failure
// where the monitor is unlocked but all the contending threads remain parked).
// That is, at least one of the contended threads will periodically poll _owner.
// One of the contending threads will become the designated "Responsible" thread.
// The Responsible thread uses a timed park instead of a normal indefinite park
// operation -- it periodically wakes and checks for and recovers from potential
// strandings admitted by 1-0 exit operations. We need at most one Responsible
// thread per-monitor at any given moment. Only threads on cxq|EntryList may
// be responsible for a monitor.
//
// Currently, one of the contended threads takes on the added role of "Responsible".
// A viable alternative would be to use a dedicated "stranding checker" thread
// that periodically iterated over all the threads (or active monitors) and unparked
// successors where there was risk of stranding. This would help eliminate the
// timer scalability issues we see on some platforms as we'd only have one thread
// -- the checker -- parked on a timer.
//这里判断的是,如果nex==null(说明cxq队列为空),并且entryList为空。
//那说明当前线程是第一个阻塞或者等待当前锁的线程
if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
// Try to assume the role of responsible thread for the monitor.
// CONSIDER: ST vs CAS vs { if (Responsible==null) Responsible=Self }
//通过cas将_Responsible指针指向Self
Atomic::cmpxchg_ptr(Self, &_Responsible, NULL);
}
// The lock might have been released while this thread was occupied queueing
// itself onto _cxq. To close the race and avoid "stranding" and
// progress-liveness failure we must resample-retry _owner before parking.
// Note the Dekker/Lamport duality: ST cxq; MEMBAR; LD Owner.
// In this case the ST-MEMBAR is accomplished with CAS().
//
// TODO: Defer all thread state transitions until park-time.
// Since state transitions are heavy and inefficient we'd like
// to defer the state transitions until absolutely necessary,
// and in doing so avoid some transitions ...
TEVENT(Inflated enter - Contention);
int nWakeups = 0;
int recheckInterval = 1;
//执行到这儿,说明线程已经被成功放在了cxq队列的头部,
//然后下面进入一个循环,只有成功获取到了锁,才能够跳出循环。
for (;;) {
//再次尝试获取锁
if (TryLock(Self) > 0) break;
assert(_owner != Self, "invariant");
//如果_Responsible指针为NULL,则再次尝试让_Responsible指向当前线程
if ((SyncFlags & 2) && _Responsible == NULL) {
Atomic::cmpxchg_ptr(Self, &_Responsible, NULL);
}
// park self
//如果_Responsible成功指向了当前线程,说明当前线程是第一个被阻塞或者等待获取锁的线程。
//则会执行一个简单的退避算法。执行有等待时间的park操作,第一次是1ms
//每次到时间后自己去尝试获取锁,获取失败后继续睡眠,每次睡眠的时间是上一次的8倍。
if (_Responsible == Self || (SyncFlags & 1)) {
TEVENT(Inflated enter - park TIMED);
Self->_ParkEvent->park((jlong) recheckInterval);
// Increase the recheckInterval, but clamp the value.
recheckInterval *= 8;
if (recheckInterval > MAX_RECHECK_INTERVAL) {
recheckInterval = MAX_RECHECK_INTERVAL;
}
} else {
//如果当前线程不是第一个阻塞或者等待锁的线程,则直接park。
TEVENT(Inflated enter - park UNTIMED);
Self->_ParkEvent->park();
}
if (TryLock(Self) > 0) break;
// The lock is still contested.
// Keep a tally of the # of futile wakeups.
// Note that the counter is not protected by a lock or updated by atomics.
// That is by design - we trade "lossy" counters which are exposed to
// races during updates for a lower probe effect.
TEVENT(Inflated enter - Futile wakeup);
// This PerfData object can be used in parallel with a safepoint.
// See the work around in PerfDataManager::destroy().
OM_PERFDATA_OP(FutileWakeups, inc());
++nWakeups;
// Assuming this is not a spurious wakeup we'll normally find _succ == Self.
// We can defer clearing _succ until after the spin completes
// TrySpin() must tolerate being called with _succ == Self.
// Try yet another round of adaptive spinning.
if ((Knob_SpinAfterFutile & 1) && TrySpin(Self) > 0) break;
// We can find that we were unpark()ed and redesignated _succ while
// we were spinning. That's harmless. If we iterate and call park(),
// park() will consume the event and return immediately and we'll
// just spin again. This pattern can repeat, leaving _succ to simply
// spin on a CPU. Enable Knob_ResetEvent to clear pending unparks().
// Alternately, we can sample fired() here, and if set, forgo spinning
// in the next iteration.
if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
Self->_ParkEvent->reset();
OrderAccess::fence();
}
if (_succ == Self) _succ = NULL;
// Invariant: after clearing _succ a thread *must* retry _owner before parking.
OrderAccess::fence();
}
//执行到这里,说明当前线程成功获取了锁,所以下面要做的事情,就是要将当前线程从当前锁的阻塞队列上去掉,
// Egress :
// Self has acquired the lock -- Unlink Self from the cxq or EntryList.
// Normally we'll find Self on the EntryList .
// From the perspective of the lock owner (this thread), the
// EntryList is stable and cxq is prepend-only.
// The head of cxq is volatile but the interior is stable.
// In addition, Self.TState is stable.
assert(_owner == Self, "invariant");
assert(object() != NULL, "invariant");
// I'd like to write:
// guarantee (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
// but as we're at a safepoint that's not safe.
//从队列中去掉当前线程的操作
UnlinkAfterAcquire(Self, &node);
if (_succ == Self) _succ = NULL;
assert(_succ != Self, "invariant");
//如果_Responsible是当前线程,则将_Responsible设置为NULL
if (_Responsible == Self) {
_Responsible = NULL;
OrderAccess::fence(); // Dekker pivot-point
}
return;
}
-
首先还是尝试获取锁,获取失败后再尝试自旋一下,自旋依旧失败,之后是真正的挂起逻辑。
-
执行真正挂起逻辑之前,首先将自己包装成一个ObjectWaiter对象,并通过CAS放在Cxq队列的头部。
-
执行真正挂起逻辑的时候,有一个指针_Responsible,当cxq队列和_EntryList为空,并且_Responsible为空的时候,说明当前线程是第一个等待锁的线程,就通过CAS将_Responsible指向当前线程。
然后,进入到一个无限循环中,只有成功获取了锁,才能退出循环。 -
如果_Responsible指向的是当前程,就通过一个简单的退避算法进行有条件的挂起,第一次1ms,第二次8ms,每次下一次都是上一次睡眠时间的8倍,但时间自动唤醒自己并尝试获取锁。
-
如果_Responsible指向的不是当前线程,则当前线程会执行park将自己挂起,只有当前得到锁的线程释放锁的时候,才有机会被唤醒并且竞争锁。
-
最后,当获取到锁之后,就会跳出循环,填出循环后需要做一些后续处理,需要把自己从等待队列中移除,如果_Responsible执行自己,需要把_Responsible置为空。
重量级锁的获取流程如下:
3.3.3.1 重量级锁的释放
重量级锁的释放,执行的是ObjectMonitor的exit方法,方法如下:
void NOINLINE ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * const Self = THREAD;
//objectMonitor的owner没有指向当前线程
if (THREAD != _owner) {
//判断objectMonitor的owner是否指向当前线程的线程栈(这个在执行enter的时候,也有判断过这种情况)
if (THREAD->is_lock_owned((address) _owner)) {
// Transmute _owner from a BasicLock pointer to a Thread address.
// We don't need to hold _mutex for this transition.
// Non-null to Non-null is safe as long as all readers can
// tolerate either flavor.
assert(_recursions == 0, "invariant");
_owner = THREAD;
_recursions = 0;
} else {
// Apparent unbalanced locking ...
// Naively we'd like to throw IllegalMonitorStateException.
// As a practical matter we can neither allocate nor throw an
// exception as ::exit() can be called from leaf routines.
// see x86_32.ad Fast_Unlock() and the I1 and I2 properties.
// Upon deeper reflection, however, in a properly run JVM the only
// way we should encounter this situation is in the presence of
// unbalanced JNI locking. TODO: CheckJNICalls.
// See also: CR4414101
TEVENT(Exit - Throw IMSX);
assert(false, "Non-balanced monitor enter/exit! Likely JNI locking");
return;
}
}
//重入次数--
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
TEVENT(Inflated exit - recursive);
return;
}
// Invariant: after setting Responsible=null an thread must execute
// a MEMBAR or other serializing instruction before fetching EntryList|cxq.
if ((SyncFlags & 4) == 0) {
_Responsible = NULL;
}
#if INCLUDE_TRACE
// get the owner's thread id for the MonitorEnter event
// if it is enabled and the thread isn't suspended
if (not_suspended && Tracing::is_event_enabled(TraceJavaMonitorEnterEvent)) {
_previous_owner_tid = SharedRuntime::get_java_tid(Self);
}
#endif
for (;;) {
assert(THREAD == _owner, "invariant");
//这里根据不同的退出策略进行退出
if (Knob_ExitPolicy == 0) {
// release semantics: prior loads and stores from within the critical section
// must not float (reorder) past the following store that drops the lock.
// On SPARC that requires MEMBAR #loadstore|#storestore.
// But of course in TSO #loadstore|#storestore is not required.
// I'd like to write one of the following:
// A. OrderAccess::release() ; _owner = NULL
// B. OrderAccess::loadstore(); OrderAccess::storestore(); _owner = NULL;
// Unfortunately OrderAccess::release() and OrderAccess::loadstore() both
// store into a _dummy variable. That store is not needed, but can result
// in massive wasteful coherency traffic on classic SMP systems.
// Instead, I use release_store(), which is implemented as just a simple
// ST on x64, x86 and SPARC.
//释放锁,这里提前释放了锁之后,没有立即从cxq或者是entryList中唤醒一个线程,外面正在抢占锁的线程可以提前获取到锁。
OrderAccess::release_store_ptr(&_owner, NULL); // drop the lock
//增加内存屏障
OrderAccess::storeload(); // See if we need to wake a successor
//后面是从cxq或者_EntryList中唤醒线程的逻辑,这里判断如果_cxq和_EntryList都是空的情况下,直接返回。
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
TEVENT(Inflated exit - simple egress);
return;
}
TEVENT(Inflated exit - complex egress);
// Other threads are blocked trying to acquire the lock.
// Normally the exiting thread is responsible for ensuring succession,
// but if other successors are ready or other entering threads are spinning
// then this thread can simply store NULL into _owner and exit without
// waking a successor. The existence of spinners or ready successors
// guarantees proper succession (liveness). Responsibility passes to the
// ready or running successors. The exiting thread delegates the duty.
// More precisely, if a successor already exists this thread is absolved
// of the responsibility of waking (unparking) one.
//
// The _succ variable is critical to reducing futile wakeup frequency.
// _succ identifies the "heir presumptive" thread that has been made
// ready (unparked) but that has not yet run. We need only one such
// successor thread to guarantee progress.
// See http://www.usenix.org/events/jvm01/full_papers/dice/dice.pdf
// section 3.3 "Futile Wakeup Throttling" for details.
//
// Note that spinners in Enter() also set _succ non-null.
// In the current implementation spinners opportunistically set
// _succ so that exiting threads might avoid waking a successor.
// Another less appealing alternative would be for the exiting thread
// to drop the lock and then spin briefly to see if a spinner managed
// to acquire the lock. If so, the exiting thread could exit
// immediately without waking a successor, otherwise the exiting
// thread would need to dequeue and wake a successor.
// (Note that we'd need to make the post-drop spin short, but no
// shorter than the worst-case round-trip cache-line migration time.
// The dropped lock needs to become visible to the spinner, and then
// the acquisition of the lock by the spinner must become visible to
// the exiting thread).
// It appears that an heir-presumptive (successor) must be made ready.
// Only the current lock owner can manipulate the EntryList or
// drain _cxq, so we need to reacquire the lock. If we fail
// to reacquire the lock the responsibility for ensuring succession
// falls to the new owner.
//
//使用CAS替换owmer为当前线程,因为后面要从cxq或者entryList中唤醒一个线程,但是只有持有当前锁的线程才能才做cxq和entryList。
//正如前面释放锁时候解释的那样,上面释放锁,是为了让外面正在抢占锁的线程提前获取锁,这里尝试替换,如果失败,说明锁已经被抢占。成功则执行唤醒逻辑。
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
return;
}
TEVENT(Exit - Reacquired);
} else {
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
OrderAccess::release_store_ptr(&_owner, NULL); // drop the lock
OrderAccess::storeload();
// Ratify the previously observed values.
if (_cxq == NULL || _succ != NULL) {
TEVENT(Inflated exit - simple egress);
return;
}
// inopportune interleaving -- the exiting thread (this thread)
// in the fast-exit path raced an entering thread in the slow-enter
// path.
// We have two choices:
// A. Try to reacquire the lock.
// If the CAS() fails return immediately, otherwise
// we either restart/rerun the exit operation, or simply
// fall-through into the code below which wakes a successor.
// B. If the elements forming the EntryList|cxq are TSM
// we could simply unpark() the lead thread and return
// without having set _succ.
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
TEVENT(Inflated exit - reacquired succeeded);
return;
}
TEVENT(Inflated exit - reacquired failed);
} else {
TEVENT(Inflated exit - complex egress);
}
}
guarantee(_owner == THREAD, "invariant");
//这里就开始真正开始进行唤醒线程的逻辑了,这里的W将是后面唤醒的线程。
ObjectWaiter * w = NULL;
int QMode = Knob_QMode;
//这里根据不同的模式进行不同的操作。
if (QMode == 2 && _cxq != NULL) {
// QMode == 2 : cxq has precedence over EntryList.
// Try to directly wake a successor from the cxq.
// If successful, the successor will need to unlink itself from cxq.
//这种模式下,如果CXQ不等于空,直接从CXQ的头部取出ObjectWaiter进行唤醒。
w = _cxq;
assert(w != NULL, "invariant");
assert(w->TState == ObjectWaiter::TS_CXQ, "Invariant");
ExitEpilog(Self, w);
return;
}
if (QMode == 3 && _cxq != NULL) {
// Aggressively drain cxq into EntryList at the first opportunity.
// This policy ensure that recently-run threads live at the head of EntryList.
// Drain _cxq into EntryList - bulk transfer.
// First, detach _cxq.
// The following loop is tantamount to: w = swap(&cxq, NULL)
w = _cxq;
//把&_cxq赋值为NULL。
for (;;) {
assert(w != NULL, "Invariant");
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr(NULL, &_cxq, w);
//如果替换成功,直接break跳出。失败则将队首的值u赋值给w,继续尝试替换。
if (u == w) break;
w = u;
}
assert(w != NULL, "invariant");
//这一段队列操作,其实就是将cxq队列转换成一个双向队列
ObjectWaiter * q = NULL;
ObjectWaiter * p;
for (p = w; p != NULL; p = p->_next) {
guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
p->TState = ObjectWaiter::TS_ENTER;
p->_prev = q;
q = p;
}
// Append the RATs to the EntryList
// TODO: organize EntryList as a CDLL so we can locate the tail in constant-time.
ObjectWaiter * Tail;
//这个操作结束,使得tail指向了_EntryList最后一个节点。
for (Tail = _EntryList; Tail != NULL && Tail->_next != NULL;
Tail = Tail->_next)
/* empty */;
//下面的操作,是将CXQ链接在_EntryList的末尾。
if (Tail == NULL) {
_EntryList = w;
} else {
Tail->_next = w;
w->_prev = Tail;
}
// Fall thru into code that tries to wake a successor from EntryList
}
if (QMode == 4 && _cxq != NULL) {
// Aggressively drain cxq into EntryList at the first opportunity.
// This policy ensure that recently-run threads live at the head of EntryList.
// Drain _cxq into EntryList - bulk transfer.
// First, detach _cxq.
// The following loop is tantamount to: w = swap(&cxq, NULL)
w = _cxq;
for (;;) {
assert(w != NULL, "Invariant");
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr(NULL, &_cxq, w);
if (u == w) break;
w = u;
}
assert(w != NULL, "invariant");
ObjectWaiter * q = NULL;
ObjectWaiter * p;
for (p = w; p != NULL; p = p->_next) {
guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
p->TState = ObjectWaiter::TS_ENTER;
p->_prev = q;
q = p;
}
// Prepend the RATs to the EntryList
//将_EntryList连接在CXQ队列的末尾
if (_EntryList != NULL) {
q->_next = _EntryList;
_EntryList->_prev = q;
}
//将_EntryList指向新的队列队首
_EntryList = w;
// Fall thru into code that tries to wake a successor from EntryList
}
//上面QMode = 3和QMode = 4的情况,都是链接一个新的队列,最后,_EntryList都指向队首。
//当cxq为空的时候,就不用组成新队列,所以这里w直接等于_EntryList即可。
w = _EntryList;
//如果w != NULL:说明_EntryList一定不为空。
if (w != NULL) {
// I'd like to write: guarantee (w->_thread != Self).
// But in practice an exiting thread may find itself on the EntryList.
// Let's say thread T1 calls O.wait(). Wait() enqueues T1 on O's waitset and
// then calls exit(). Exit release the lock by setting O._owner to NULL.
// Let's say T1 then stalls. T2 acquires O and calls O.notify(). The
// notify() operation moves T1 from O's waitset to O's EntryList. T2 then
// release the lock "O". T2 resumes immediately after the ST of null into
// _owner, above. T2 notices that the EntryList is populated, so it
// reacquires the lock and then finds itself on the EntryList.
// Given all that, we have to tolerate the circumstance where "w" is
// associated with Self.
assert(w->TState == ObjectWaiter::TS_ENTER, "invariant");
ExitEpilog(Self, w);
return;
}
//执行到这儿,说明_EntryList一定为空。但是CXQ不一定为空,因为QMode可能不等于3或者4。
// If we find that both _cxq and EntryList are null then just
// re-run the exit protocol from the top.
//判断CXQ是否为空
w = _cxq;
//如果CXQ也是空,则conntinue,continue后应该还记得前面有释放owner后判断cxq和entryList的,从哪儿直接就退出了。
if (w == NULL) continue;
// Drain _cxq into EntryList - bulk transfer.
// First, detach _cxq.
// The following loop is tantamount to: w = swap(&cxq, NULL)
//执行到这儿说明CXQ肯定是不为空的,说明QMode不等于3或者4
//将cxq赋值为空
for (;;) {
assert(w != NULL, "Invariant");
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr(NULL, &_cxq, w);
if (u == w) break;
w = u;
}
TEVENT(Inflated exit - drain cxq into EntryList);
assert(w != NULL, "invariant");
assert(_EntryList == NULL, "invariant");
// Convert the LIFO SLL anchored by _cxq into a DLL.
// The list reorganization step operates in O(LENGTH(w)) time.
// It's critical that this step operate quickly as
// "Self" still holds the outer-lock, restricting parallelism
// and effectively lengthening the critical section.
// Invariant: s chases t chases u.
// TODO-FIXME: consider changing EntryList from a DLL to a CDLL so
// we have faster access to the tail.
if (QMode == 1) {
// QMode == 1 : drain cxq to EntryList, reversing order
// We also reverse the order of the list.
ObjectWaiter * s = NULL;
ObjectWaiter * t = w;
ObjectWaiter * u = NULL;
//将CXQ队列转换成双向队列。并且将_EntryList进行了反转(颠倒顺序)
while (t != NULL) {
guarantee(t->TState == ObjectWaiter::TS_CXQ, "invariant");
t->TState = ObjectWaiter::TS_ENTER;
u = t->_next;
t->_prev = u;
t->_next = s;
s = t;
t = u;
}
_EntryList = s;
assert(s != NULL, "invariant");
} else {
// QMode == 0 or QMode == 2
//这种情况,也就是把cxq变成了双向队列。_EntryList指向队首。
_EntryList = w;
ObjectWaiter * q = NULL;
ObjectWaiter * p;
for (p = w; p != NULL; p = p->_next) {
guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
p->TState = ObjectWaiter::TS_ENTER;
p->_prev = q;
q = p;
}
}
// In 1-0 mode we need: ST EntryList; MEMBAR #storestore; ST _owner = NULL
// The MEMBAR is satisfied by the release_store() operation in ExitEpilog().
// See if we can abdicate to a spinner instead of waking a thread.
// A primary goal of the implementation is to reduce the
// context-switch rate.
if (_succ != NULL) continue;
w = _EntryList;
if (w != NULL) {
guarantee(w->TState == ObjectWaiter::TS_ENTER, "invariant");
ExitEpilog(Self, w);
return;
}
}
}
方法内容较多,单整体逻辑不难理解:
1、首先判断owner是否是当前线程,因为只有获得当前锁的线程,才能执行exit。
2、对重入次数进行减1操作。
3、接下来是个无限循环,只有成功释放了锁,才会跳出循环。在循环中,主要做的事情是:
(1)首先根据不同的退出策略执行了一段不同的逻辑,但是大致做的事情都差不多。只是Knob_ExitPolicy != 0的时候,先判断了cxq队列和_EntryList如果都是空才进行提前释放锁操作。这里提前对锁进行了释放,然后判断了cxq队列和_EntryList是否都是空,如果是,就直接返回了,因为没有等待锁的线程需要去唤醒。如果不为空,就会继续执行,开始唤醒等待在cxq或者_EntryList上的一个线程。但是要操作cxq和_EntryList必须要重新获得当前锁,所以这里进行了锁的重新获取,但是如果这里获取锁失败了,说明锁已经被其他线程抢占了,就直接返回了。
(2)因为唤醒一个CXQ和_EntryList上的等待锁的线程,具体应该唤醒哪一个呢?所以这里通过不同的策略,即QModel的不同,进行了不同的唤醒线程的选择。
QModel = 2: 这种情况,直接从CXQ的头部取出一个线程,然后执行ExitEpilog进行唤醒,ExitEpilog其实就做了两件事,一是将owner设置为NULL,二是通过unpark唤醒线程,后面能看到源码。
QModel = 3: 这种情况,把CXQ转换成了一个双向队列,然后连接在_EntryList的末尾。
QModel = 4: 这种情况,把CXQ转换成了一个双向队列,然后连接在_EntryList的头部。
上面这三种方式,必须是CXQ不为空的情况下才会执行,也只有CXQ不为空才有执行的必要,其中,第二种方式,如果CXQ不为空,直接执行唤醒然后返回了。
3,4两种情况下,会得到一个新的EntryList队列,然后继续往下执行。
继续往下执行w = _EntryList,这一步操作,如果QModel =3 或者 QModel = 4,则w指向新的队列,如果QModel既不是3也不是4,w就指向原本的EntryList。
所以如果W != NULL,说明Entry一定不等于NULL,CXQ是否为NULL是不确定的,这种情况下,只有QModel = 4才会首先唤醒原CXQ队列头。其他情况,都会优先唤醒_EntryList的的头节点。
如果W == NULL,说明EntryList一定是NULL,CXQ不一定是NULL。所以首先就判断CXQ是否是NULL,如果是,说明EntryList和CXQ都是NULL,就没必要继续执行唤醒了,直接continue重新判断,根据前面的判断会退出循环。
QModel = 1: 这种情况,将CXQ转换成双向队列,然后将_EntryList指向新的CXQ尾部(相当于将CXQ队列反转)。
QModel = 0: 这种情况,将CXQ转换成双向队列,将_EntryList指向新的CXQ头部。
执行完QModel = 1和QModel = 0的逻辑后,最后唤醒_EntryList执行的第一个线程。
JVM默认的QModel = 0。
重量级锁的释放过程如下:
关于Wait/Notify/NotifyAll的实现,请看Wait与notify/notifyAll源码分析
参考:
<<深入理解JVM虚拟机>>
https://createchance.github.io/post/java-并发之基石篇/
https://github.com/farmerjohngit/myblog/issues/13