目录
一、synchronized用法
1、修饰实例方法
2、修饰静态方法
3、修饰代码块
二、synchronized底层实现
1、修改代码块的字节码分析
2、monitorenter 指令实现
3、InterpreterRuntime::monitorenter
4、monitorexit 指令实现
5、InterpreterRuntime::monitorexit
6、monitorenter/ monitorexit 指令的编译执行
本篇博客来从字节码实现层面详细探讨synchronized与volatile关键字的实现细节。
修饰实例方法时,执行该方法时必须获取某个实例关联的锁,即并发调用同一实例的多个被修饰的实例方法时只能执行其中的某一个,测试用例如下:
public class AddTest {
private int a;
synchronized int add(){
a++;
return a;
}
synchronized int add2(){
a+=2;
return a;
}
}
private final long NUM=100000000;
@Test
public void test() throws Exception {
AddTest a=new AddTest();
Thread t=new Thread(new Runnable() {
@Override
public void run() {
long startTime= System.currentTimeMillis();
for(int i=0;i"+time);
}
});
t.start();
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
long startTime= System.currentTimeMillis();
for(int i=0;i"+time);
}
});
t2.start();
t.join();
t2.join();
System.out.println("job end");
}
@Test
public void test2() throws Exception {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i"+time);
}
});
t.start();
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i"+time);
}
});
t2.start();
t.join();
t2.join();
System.out.println("job end");
}
第一个测试用例就是并发调用同一个实例的两个被修饰的实例方法,其执行结果如下:
第二个测试用例是并发调用不同实例的两个被修饰的实例方法,其执行结果如下:
第一个测试用例因为两个方法在同一时间只能执行其中一个,而第二个测试用例是两个方法在并行执行互不影响,因此第一个的耗时是第二个的两倍多,超过两倍的耗时是获取锁释放锁等同步操作的损耗。
修饰静态方法时,执行该方法时必须获取该类的class如String.class实例关联的锁,即同一时间多个不同实例并发调用不同的静态方法时只能执行其中的一个方法,测试用例如下:
public class AddTest {
private int a;
private static int b;
synchronized int add(){
a++;
return a;
}
synchronized int add2(){
a+=2;
return a;
}
synchronized static int add3(){
b++;
return b;
}
synchronized static int add4(){
b+=2;
return b;
}
}
@Test
public void test2() throws Exception {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i"+time);
}
});
t.start();
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i"+time);
}
});
t2.start();
t.join();
t2.join();
System.out.println("job end");
}
@Test
public void test3() throws Exception {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i"+time);
}
});
t.start();
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
AddTest a=new AddTest();
long startTime= System.currentTimeMillis();
for(int i=0;i"+time);
}
});
t2.start();
t.join();
t2.join();
System.out.println("job end");
}
test2的耗时不变,如下图:
test3的耗时跟test1差不过了,如下图:
这是因为他们都是同一时间只能执行其中的某一个方法,不同的是test1是获取某个实例关联的锁,test3是获取类的class实例关联的锁。
修饰代码块时需要指定在哪个实例上同步,如果该实例是某个类的class实例或者静态属性时,则同样的,该类的多个不同实例并发调用该代码块时同一时间只能其中一个能执行;如果该实例是某个实例属性或者this时,则同样的,并发调用同一实例的该代码块时只能执行其中一个;如果该实例是局部变量,因为局部变量是执行该方法时私有的,其他线程不会访问到该变量,也就不会去获取该实例关联的锁了,所以实际相当于没有加锁,JVM优化时会将该锁自动消除。
测试用例如下:
public class AddTest {
private int a;
private static int b;
int add(){
synchronized (this) {
a++;
}
return a;
}
int add2(){
synchronized (this) {
a+=2;
}
return a;
}
static int add3(){
synchronized (AddTest.class) {
b++;
}
return b;
}
static int add4(){
synchronized (AddTest.class) {
b+=2;
}
return b;
}
}
public class AddTest {
private int a;
private static int b;
private Object lock=new Object();
private static Object staticLock=new Object();
int add(){
synchronized (lock) {
a++;
}
return a;
}
int add2(){
synchronized (lock) {
a+=2;
}
return a;
}
static int add3(){
synchronized (staticLock) {
b++;
}
return b;
}
static int add4(){
synchronized (staticLock) {
b+=2;
}
return b;
}
}
上述两种情形下,执行test2和test3的结果和修饰方法时的结果是一样的。
synchronized的底层实现分为两种情形,一种是修饰方法,在JVM解释或者编译执行时发现如果某个方法声明中包含有synchronized关键字就会执行对应的同步逻辑,注意实例方法与静态方法在具体执行时其区别只有一个,前者会默认将当前实例作为方法入参传进去,后者则不做任何处理;另一种是修饰代码块,这个是通过特定字节码来实现的,下面来分别说明。
测试用例如下:
public class AddTest {
private int a;
private static int b;
int add(){
synchronized (this) {
a++;
}
return a;
}
static int add2(){
synchronized (AddTest.class) {
b++;
}
return b;
}
}
将上述代码编译后,通过javap -v获取其具体的字节码信息,结果如下:
int add();
descriptor: ()I
flags:
Code:
stack=3, locals=3, args_size=1
0: aload_0 //从局部变量表中加载第0个引用类型元素放到操作数栈栈顶,第0个元素是当前实例的引用,方法开始执行前由JVM放入进去的
1: dup //复制操作数栈顶的值并将其放到操作数栈顶
2: astore_1 //将操作数栈栈顶的值出栈并保存到局部变量表中索引为1的位置
3: monitorenter //获取锁
4: aload_0
5: dup
6: getfield #2 //读取属性a,放到操作数栈栈顶
9: iconst_1 //将常量1放入到操作数栈栈顶
10: iadd //将栈顶的两个操作数出栈,执行加法运算,将结果保存在栈顶中
11: putfield #2 //将结果写入到属性a中
14: aload_1 //从局部变量表中加载第1个引用类型元素放到操作数栈栈顶
15: monitorexit //释放锁
16: goto 24 //跳转到24即aload_0指令处开始执行
19: astore_2 //如果从4到16的字节码执行过程中出现异常,异常处理器会跳转到第19处的字节码指令,此时栈顶保存着具体的异常实例
20: aload_1
21: monitorexit //释放锁
22: aload_2
23: athrow //抛出异常,终止方法执行
24: aload_0
25: getfield #2 //读取属性a放入栈顶
28: ireturn
Exception table: //从code属性的exception_table属性中解析出来的,from和to是try/catch的起始范围,target是捕获异常后的处理指令,type是捕获异常的异常类型
from to target type
4 16 19 any
19 22 19 any
LineNumberTable: //该方法的LineNumberTable属性
line 9: 0
line 10: 4
line 11: 14
line 12: 24
LocalVariableTable://局部变量表,对应于方法的LocalVariableTable属性
Start Length Slot Name Signature
0 29 0 this LsynchronizedTest/AddTest;
StackMapTable: number_of_entries = 2 //对应于方法的StackMapTable属性,用于JVM的类型检查
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class synchronizedTest/AddTest, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
static int add2();
descriptor: ()I
flags: ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: ldc #3 从运行时常量池中提取AddTest.class放到操作数栈栈顶
2: dup
3: astore_0
4: monitorenter //获取锁
5: getstatic #4 //读取静态属性b
8: iconst_1
9: iadd
10: putstatic #4 //将结果写入静态属性b
13: aload_0
14: monitorexit //释放锁
15: goto 23 //跳转到23处的指令执行
18: astore_1 //如果出现异常跳转到此处执行,操作数栈顶保存了异常Exception实例
19: aload_0
20: monitorexit //释放锁
21: aload_1
22: athrow //抛出异常
23: getstatic #4 // Field b:I
26: ireturn
Exception table:
from to target type
5 15 18 any
18 21 18 any
LineNumberTable:
line 16: 0
line 17: 5
line 18: 13
line 19: 23
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 18
locals = [ class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
由上述分析可知,synchronized修饰代码块时会被翻译成monitorenter和monitorexit指令对,monitorenter用于获取锁,monitorexit用于释放锁,并且还会自动加上异常处理逻辑,会捕获所有的异常,捕获完成后就通过monitorexit释放锁并抛出异常。
如何查看各字节码的底层实现可以参考《Hotspot 方法调用之TemplateInterpreter 源码解析》,monitorenter 指令的实现如下:
void TemplateTable::monitorenter() {
//校验当前指令的栈顶缓存类型是否正确
transition(atos, vtos);
//校验rax中值是否为空,栈顶缓存就保存在rax寄存器中,如果为NULL会触发底层操作系统的NULL异常
//此时rax中保存的是用于获取锁的实例oop
__ null_check(rax);
const Address monitor_block_top(
rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize);
const Address monitor_block_bot(
rbp, frame::interpreter_frame_initial_sp_offset * wordSize);
const int entry_size = frame::interpreter_frame_monitor_size() * wordSize;
Label allocated;
//xorl用于按位异或,相同的位置为0,不同的位置为1,此处是将c_rarg1置为NULL
__ xorl(c_rarg1, c_rarg1); // points to free slot or NULL
//找到一个空闲的monitor_block,结果保存在c_rarg1中
{
Label entry, loop, exit;
//将monitor_block_top拷贝到c_rarg3中
__ movptr(c_rarg3, monitor_block_top); // points to current entry,
// starting with top-most entry
//将monitor_block_bot拷贝到c_rarg2
__ lea(c_rarg2, monitor_block_bot); // points to word before bottom
// of monitor block
//跳转到entry标签处执行
__ jmpb(entry);
__ bind(loop);
//判断c_rarg3指向的BasicObjectLock的obj属性是否为空,如果为空表示未使用
__ cmpptr(Address(c_rarg3, BasicObjectLock::obj_offset_in_bytes()), (int32_t) NULL_WORD);
//如果相等,即BasicObjectLock的obj属性为空,则将c_rarg3的值拷贝到c_rarg1
__ cmov(Assembler::equal, c_rarg1, c_rarg3);
// 判断c_rarg3指向的BasicObjectLock的obj属性与rax中实例是否一致
__ cmpptr(rax, Address(c_rarg3, BasicObjectLock::obj_offset_in_bytes()));
// 如果一致则退出,一致说明BasicObjectLock的obj属性不为空,此时c_rarg1为空,就是重新分配一个新的
__ jccb(Assembler::equal, exit);
// 如果不一致则把c_rarg3地址加上entry_size,即开始遍历前面一个monitor_block,即存在空闲的,但是没有obj属性相同的时候会把所有的
//BasicObjectLock都遍历一遍,找到最上面的地址最大一个空闲的BasicObjectLock
__ addptr(c_rarg3, entry_size);
__ bind(entry);
//判断两个寄存器的值是否相等
__ cmpptr(c_rarg3, c_rarg2);
//如果不等于则跳转到loop标签,否则跳转到exit
__ jcc(Assembler::notEqual, loop);
__ bind(exit);
}
//判断c_rarg1是否为空,如果不为空则跳转到allocated处
__ testptr(c_rarg1, c_rarg1); // check if a slot has been found
__ jcc(Assembler::notZero, allocated); // if found, continue with that one
//如果没有找到空闲的monitor_block则分配一个
{
Label entry, loop;
// 将monitor_block_bot拷贝到c_rarg1 // rsp: old expression stack top
__ movptr(c_rarg1, monitor_block_bot); // c_rarg1: old expression stack bottom
//向下(低地址端)移动rsp指针entry_size字节
__ subptr(rsp, entry_size); // move expression stack top
//将c_rarg1减去entry_size
__ subptr(c_rarg1, entry_size); // move expression stack bottom
//将rsp拷贝到c_rarg3
__ mov(c_rarg3, rsp); // set start value for copy loop
//将c_rarg1中的值写入到monitor_block_bot
__ movptr(monitor_block_bot, c_rarg1); // set new monitor block bottom
//跳转到entry处开始循环
__ jmp(entry);
// 2.移动monitor_block_bot到栈顶的数据,将从栈顶分配的一个monitor_block插入到原来的monitor_block_bot下面
__ bind(loop);
//将c_rarg3之后的entry_size处的地址拷贝到c_rarg2,即原来的rsp地址
__ movptr(c_rarg2, Address(c_rarg3, entry_size)); // load expression stack
// word from old location
//将c_rarg2中的数据拷贝到c_rarg3处,即新的rsp地址
__ movptr(Address(c_rarg3, 0), c_rarg2); // and store it at new location
//c_rarg3加上一个字宽,即准备复制下一个字宽的数据
__ addptr(c_rarg3, wordSize); // advance to next word
__ bind(entry);
//比较两个寄存器的值
__ cmpptr(c_rarg3, c_rarg1); // check if bottom reached
//如果不等于则跳转到loop
__ jcc(Assembler::notEqual, loop); // if not at bottom then
// copy next word
}
// call run-time routine
// c_rarg1: points to monitor entry
__ bind(allocated);
//增加r13,使其指向下一个字节码指令
__ increment(r13);
//将rax中保存的获取锁的oop保存到c_rarg1指向的BasicObjectLock的obj属性中
__ movptr(Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()), rax);
//获取锁
__ lock_object(c_rarg1);
//保存bcp,为了出现异常时能够返回到原来的执行位置
__ save_bcp(); // in case of exception
__ generate_stack_overflow_check(0);
//恢复字节码指令的正常执行
//因为上面已经增加r13了,所以此处dispatch_next的第二个参数使用默认值0,即执行r13指向的字节码指令即可,不用跳转到下一个指令
__ dispatch_next(vtos);
}
void InterpreterMacroAssembler::lock_object(Register lock_reg) {
assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1");
//UseHeavyMonitors表示是否只使用重量级锁,默认为false,如果为true则调用InterpreterRuntime::monitorenter方法获取重量级锁
if (UseHeavyMonitors) {
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
lock_reg);
} else {
Label done;
const Register swap_reg = rax; // Must use rax for cmpxchg instruction
const Register obj_reg = c_rarg3; // Will contain the oop
const int obj_offset = BasicObjectLock::obj_offset_in_bytes();
const int lock_offset = BasicObjectLock::lock_offset_in_bytes ();
const int mark_offset = lock_offset +
BasicLock::displaced_header_offset_in_bytes();
Label slow_case;
//进入此方法目标obj要么是无锁状态,要么是对同一个对象的synchronized嵌套情形下的有锁状态
//将用于获取锁的实例oop拷贝到obj_reg中
movptr(obj_reg, Address(lock_reg, obj_offset));
//UseBiasedLocking默认为true
if (UseBiasedLocking) {
//首先尝试获取偏向锁,获取成功会跳转到done,否则走到slow_case
biased_locking_enter(lock_reg, obj_reg, swap_reg, rscratch1, false, done, &slow_case);
}
//如果UseBiasedLocking为false或者目标对象的锁不是偏向锁了会走此逻辑
movl(swap_reg, 1);
//计算 object->mark() | 1,结果保存到swap_reg,跟1做或运算将其标记为无锁状态
orptr(swap_reg, Address(obj_reg, 0));
//将(object->mark() | 1)的结果保存到BasicLock的displaced_header中,保存原来的对象头
movptr(Address(lock_reg, mark_offset), swap_reg);
//lock_reg即是里面的lock属性的地址
assert(lock_offset == 0,
"displached header must be first word in BasicObjectLock");
if (os::is_MP()) lock(); //如果是多核系统,通过lock指令保证cmpxchgp的操作是原子的,即只可能有一个线程操作obj对象头
//将obj的对象头同rax即swap_reg比较,如果相等将lock_reg写入obj对象头,即lock属性写入obj对象头,如果不等于则将obj对象头放入rax中
cmpxchgptr(lock_reg, Address(obj_reg, 0));
if (PrintBiasedLockingStatistics) {
//增加计数器
cond_inc32(Assembler::zero,
ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));
}
//如果等于,说明obj的对象头是无锁状态的,此时跟1做或运算,结果不变
jcc(Assembler::zero, done);
//如果不等于,说明obj的对象头要么是偏向锁,要么是重量级锁,多线程下可能其他线程已经获取了该对象的轻量级锁
//下面的汇编指令相当于执行如下判断,判断目标对应的对象头是否属于当前调用栈帧,如果是说明还是当前线程占有该轻量级锁,如果不是则说明其他线程占用了轻量级锁或者已经膨胀成重量级锁
// 1) (mark & 7) == 0, and
// 2) rsp <= mark < mark + os::pagesize()
subptr(swap_reg, rsp);
andptr(swap_reg, 7 - os::vm_page_size());
//在递归即synchronized嵌套使用的情形下,上述指令计算的结果就是0
//当BasicLock的displaced_header置为NULL
movptr(Address(lock_reg, mark_offset), swap_reg);
if (PrintBiasedLockingStatistics) {
//增加计数器
cond_inc32(Assembler::zero,
ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));
}
//如果andptr的结果为0,说明当前线程已经获取了轻量级锁则跳转到done
jcc(Assembler::zero, done);
//否则执行InterpreterRuntime::monitorenter将轻量级锁膨胀成重量级锁或者获取重量级锁
bind(slow_case);
// Call the runtime routine for slow case
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
lock_reg);
bind(done);
}
}
其中BasicObjectLock是一个简单的数据结构,其定义如下:
BasicLock主要用于保存对象的对象头,其定义如下:
BasicObjectLock是内嵌在线程的调用栈帧中的,有一段连续的内存区域用来保存多个BasicObjectLock,这个内存区域的起始位置是保存在栈帧中的特定偏移处的,终止地址(低地址端)保存interpreter_frame_monitor_block_top_offset偏移处,起始地址(高地址端)保存在interpreter_frame_initial_sp_offset偏移处。monitorenter方法会遍历用于分配BasicObjectLock的连续内存区域,注意是从低地址端往高地址端遍历,即按照BasicObjectLock的分配顺序倒序遍历,先遍历最近分配的BasicObjectLock。遍历过程中,如果找到一个obj属性就是目标对象的BasicObjectLock,则停止遍历重新分配一个新的BasicObjectLock;如果没有找到obj属性相同的且没有空闲的,同样重新分配一个新的BasicObjectLock;如果没有obj属性相同的且有空闲的,则遍历完所有的BasicObjectLock,找到最上面的地址最高的一个空闲BasicObjectLock,总而言之就是要确保新分配的BasicObjectLock一定要在之前分配的obj属性是同一个对象的BasicObjectLock的后面,因为解锁时也是倒序查找的,找到一个obj属性相同的视为查找成功。获取BasicObjectLock后就将当前对象保存进obj属性,然后调用lock_object获取锁。在默认开启UseBiasedLocking时,lock_object会先获取偏向锁,如果已经获取了则升级成轻量级锁,如果已经获取了轻量级锁则升级成重量级锁。
InterpreterRuntime::monitorenter用于获取轻量级锁或者重量级锁,获取轻量级锁成功后会将目标对象的对象头改成BasicLock指针,获取重量级锁成功后会将目标对象的对象头改成ObjectMonitor指针,BasicLock和ObjectMonitor本身会保存目标对象原来的无锁状态下的对象头,其实现如下:
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
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) {
//如果使用偏向锁,走快速enter
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
"must be NULL or an object");
IRT_END
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) {
if (!SafepointSynchronize::is_at_safepoint()) {
//如果不是在安全点上,则撤销偏向锁,特殊情形下会重新获取偏向锁
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
//获取成功直接返回
return;
}
} else {
assert(!attempt_rebias, "can not rebias toward VM thread");
//如果在安全点下,撤销偏向锁
BiasedLocking::revoke_at_safepoint(obj);
}
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
slow_enter (obj, lock, THREAD) ;
}
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()) {
//未持有锁,保存原来的对象头
lock->set_displaced_header(mark);
//将lock作为原来的对象头,因为BasicLock本身的大小就是8字节的,所以lock地址的后3位都是0,0就表示轻量级锁
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
//如果设置成功表示已经获取轻量级锁,返回
return ;
}
//修改失败,说明有其他线程获取了同一个对象的轻量级锁,则需要将其膨胀成重量级锁
} else if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
//如果该对象已经持有轻量级锁且对象头中包含的指针属于当前线程所有,即是当前线程持有了该对象的轻量级锁
assert(lock != mark->locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
//已经分配了对应的BasicLock,这个BasicLock没有用了
lock->set_displaced_header(NULL);
return;
}
//因为ObjectMonitor中会保存对象的对象头,所以此处不需要在保存了
lock->set_displaced_header(markOopDesc::unused_mark());
//锁膨胀转换成重量级锁
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
bool Thread::is_lock_owned(address adr) const {
return on_local_stack(adr);
}
BasicLock* locker() const {
assert(has_locker(), "check");
return (BasicLock*) value();
}
对比lock_obj方法可知,两者获取轻量级锁或者处理锁嵌套情形时的代码是一样的,monitorenter方法增加了一步撤销偏向锁和轻量级锁膨胀成重量级锁的逻辑,这是为了兼容UseHeavyMonitors参数为true或者UseBiasedLocking为false时的处理逻辑。
monitorexit指令用于释放锁,其实现如下:
void TemplateTable::monitorexit() {
//检查栈顶缓存的类型是否正确
transition(atos, vtos);
//检查rax包含的跟锁关联的对象oop是否为空
__ null_check(rax);
const Address monitor_block_top(
rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize);
const Address monitor_block_bot(
rbp, frame::interpreter_frame_initial_sp_offset * wordSize);
const int entry_size = frame::interpreter_frame_monitor_size() * wordSize;
Label found;
// find matching slot
{
Label entry, loop;
//把monitor_block_top拷贝到c_rarg1
__ movptr(c_rarg1, monitor_block_top); // points to current entry,
// starting with top-most entry
//把monitor_block_bot拷贝到c_rarg2
__ lea(c_rarg2, monitor_block_bot); // points to word before bottom
// of monitor block
__ jmpb(entry);
__ bind(loop);
//比较rax中对象oop与obj属性是否一致
__ cmpptr(rax, Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()));
//如果一致则表示找到了跳转到found
__ jcc(Assembler::equal, found);
//如果没有找到则增加entry_size,即开始遍历前面一个BasicObjectLock
__ addptr(c_rarg1, entry_size);
__ bind(entry);
//比较这两个是否相等,如果相等表示遍历完成
__ cmpptr(c_rarg1, c_rarg2);
//如果不等则跳转到loop标签
__ jcc(Assembler::notEqual, loop);
}
//没有在当前线程的栈帧中找到关联的BasicObjectLock,抛出异常
__ call_VM(noreg, CAST_FROM_FN_PTR(address,
InterpreterRuntime::throw_illegal_monitor_state_exception));
__ should_not_reach_here();
// call run-time routine
// rsi: points to monitor entry
__ bind(found);
//将这个锁对象放入栈帧中
__ push_ptr(rax); // make sure object is on stack (contract with oopMaps)
//执行解锁逻辑
__ unlock_object(c_rarg1);
//从栈帧中弹出锁对象
__ pop_ptr(rax); // discard object
}
void InterpreterMacroAssembler::unlock_object(Register lock_reg) {
assert(lock_reg == c_rarg1, "The argument is only for looks. It must be rarg1");
if (UseHeavyMonitors) {
//如果只使用重量级锁,UseHeavyMonitors默认为false
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit),
lock_reg);
} else {
Label done;
const Register swap_reg = rax; // Must use rax for cmpxchg instruction
const Register header_reg = c_rarg2; // Will contain the old oopMark
const Register obj_reg = c_rarg3; // Will contain the oop
save_bcp(); //保存bcp,方便解锁异常时回滚
//将lock属性的地址复制到swap_reg
lea(swap_reg, Address(lock_reg, BasicObjectLock::lock_offset_in_bytes()));
//将obj属性复制到obj_reg
movptr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()));
//将obj属性置为NULL
movptr(Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()), (int32_t)NULL_WORD);
if (UseBiasedLocking) {
//如果持有偏向锁,则解锁完成后跳转到done
biased_locking_exit(obj_reg, header_reg, done);
}
//将BasicLock的displaced_header属性复制到header_reg中,即该对象原来的对象头
movptr(header_reg, Address(swap_reg,
BasicLock::displaced_header_offset_in_bytes()));
//判断这个是否为空
testptr(header_reg, header_reg);
//如果为空说明这是对同一目标对象的synchronized嵌套情形,则跳转到done,等到外层的synchronized解锁恢复目标对象的对象头
jcc(Assembler::zero, done);
// Atomic swap back the old header
if (os::is_MP()) lock();//如果是多核系统则通过lock指令前缀将cmpxchg变成一个原子操作,即只能有一个线程同时操作obj的对象头
//将obj的对象头同rax即swap_reg,即lock属性地址比较,如果相等把header_reg写入到obj的对象头中即恢复对象头,如果不等把obj对象头写入rax中
cmpxchgptr(header_reg, Address(obj_reg, 0));
//如果相等,说明还是轻量级锁,解锁完成
jcc(Assembler::zero, done);
//如果不等于,说明轻量级锁被膨胀成重量级锁,恢复obj属性,因为上面将该属性置为NULL
movptr(Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()),
obj_reg); // restore obj
//调用InterpreterRuntime::monitorexit,完成重量级锁退出
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit),
lock_reg);
bind(done);
//恢复bcp
restore_bcp();
}
}
//偏向锁的解锁只是判断目标对象是否持有偏向锁,如果持有就跳转到done,没有实际的解锁动作
void MacroAssembler::biased_locking_exit(Register obj_reg, Register temp_reg, Label& done) {
assert(UseBiasedLocking, "why call this otherwise?");
//将obj的对象头拷贝到temp_reg
movptr(temp_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes()));
//将对象头指针同biased_lock_mask_in_place求且
andptr(temp_reg, markOopDesc::biased_lock_mask_in_place);
//判断且运算后的结果是否是5
cmpptr(temp_reg, markOopDesc::biased_lock_pattern);
//如果相等则跳转到done
jcc(Assembler::equal, done);
}
monitorexit用于轻量级锁和重量级锁的释放,锁释放就是目标对象的对象头恢复成无锁状态,其实现如下:
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
//如果BasicObjectLock为空或者目标对象没有持有锁则抛出异常
if (elem == NULL || h_obj()->is_unlocked()) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
//将obj置为NULL
elem->set_obj(NULL);
IRT_END
inline bool oopDesc::is_unlocked() const {
return mark()->is_unlocked();
}
bool is_unlocked() const {
return (mask_bits(value(), biased_lock_mask_in_place) == unlocked_value);
}
void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
fast_exit (object, lock, THREAD) ;
}
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 ;
if (dhw == NULL) {
//对同一目标对象的synchronized嵌套情形,该对象的原来的对象头保存在外层synchronized对应的BasicLock或者ObjectMonitor中,外层synchronized解锁时会恢复其对象头
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 ;
}
mark = object->mark() ;
if (mark == (markOop) lock) {
//持有轻量级锁,dhw保存着原来的对象头
assert (dhw->is_neutral(), "invariant") ;
//恢复对象的对象头
if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
TEVENT (fast_exit: release stacklock) ;
return;
}
//此时持有该对象轻量级锁的只有当前线程,所以原子修改不会失败
}
//如果持有重量级锁,则重量锁解锁
ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}
对比unlock_obj方法的实现,两者轻量级锁解锁和锁嵌套情形下解锁的逻辑是一样的, monitorexit方法增加了重量级锁解锁的处理逻辑
上述几个小节描述的都是在解释器下的执行逻辑,那么编译器编译后的执行逻辑了?参考ObjectSynchronizer::fast_enter和fast_exit的调用链,如下:
其中Runtime1就是C1编译器,即client编译器的运行时支持,OptoRuntime就是C2编译器即server编译器的运行时支持,SharkRuntime是OpenJDK特有的Shark编译器的运行时支持,这三个的实现逻辑基本一致,以 Runtime1为例说明:
JRT_ENTRY_NO_ASYNC(void, Runtime1::monitorenter(JavaThread* thread, oopDesc* obj, BasicObjectLock* lock))
Handle h_obj(thread, obj);
assert(h_obj()->is_oop(), "must be NULL or an object");
if (UseBiasedLocking) {
//UseBiasedLocking默认为true,
ObjectSynchronizer::fast_enter(h_obj, lock->lock(), true, CHECK);
} else {
if (UseFastLocking) {
//UseFastLocking默认为true
assert(obj == lock->obj(), "must match");
ObjectSynchronizer::slow_enter(h_obj, lock->lock(), THREAD);
} else {
lock->set_obj(obj);
ObjectSynchronizer::fast_enter(h_obj, lock->lock(), false, THREAD);
}
}
JRT_END
JRT_LEAF(void, Runtime1::monitorexit(JavaThread* thread, BasicObjectLock* lock))
assert(thread == JavaThread::current(), "threads must correspond");
assert(thread->last_Java_sp(), "last_Java_sp must be set");
// monitorexit is non-blocking (leaf routine) => no exceptions can be thrown
EXCEPTION_MARK;
oop obj = lock->obj();
assert(obj->is_oop(), "must be NULL or an object");
if (UseFastLocking) {
//UseFastLocking默认为true
ObjectSynchronizer::slow_exit(obj, lock->lock(), THREAD);
} else {
ObjectSynchronizer::fast_exit(obj, lock->lock(), THREAD);
}
JRT_END
其直接调用在Runtime1::generate_code_for方法中,用于生成特定方法的执行Stub,具体如下:
其中save_live_registers用于保存寄存器中的数据,restore_live_registers用于将原来保存的数据恢复到寄存器中。可以进一步搜索monitorenter_id的调用链,如下:
MonitorEnterStub::emit_code和MonitorExitStub::emit_code在一起的,其实现如下:
_obj_reg和_lock_reg都是父类的属性,通过构造函数传递进来的,查看MonitorEnterStub的构造函数的调用链,如下:
其中LIRGenerator::monitor_enter是处理synchronized修饰代码块情形的,LIRGenerator::do_Base中的调用是处理synchronized修饰方法情形的,前者的实现如下:
load_stack_address_monitor用于查找一个空闲的或者分配一个新的BasicObjectLock,lock_object的实现方法是C1_MacroAssembler::lock_object,如下:
int C1_MacroAssembler::lock_object(Register hdr, Register obj, Register disp_hdr, Register scratch, Label& slow_case) {
const int aligned_mask = BytesPerWord -1;
const int hdr_offset = oopDesc::mark_offset_in_bytes();
assert(hdr == rax, "hdr must be rax, for the cmpxchg instruction");
assert(hdr != obj && hdr != disp_hdr && obj != disp_hdr, "registers must be different");
Label done;
int null_check_offset = -1;
verify_oop(obj);
// save object being locked into the BasicObjectLock
movptr(Address(disp_hdr, BasicObjectLock::obj_offset_in_bytes()), obj);
if (UseBiasedLocking) {
assert(scratch != noreg, "should have scratch register at this point");
//跟解释器调用同样的方法
null_check_offset = biased_locking_enter(disp_hdr, obj, hdr, scratch, false, done, &slow_case);
} else {
null_check_offset = offset();
}
// Load object header
movptr(hdr, Address(obj, hdr_offset));
//unlocked_value就是1
orptr(hdr, markOopDesc::unlocked_value);
// save unlocked object header into the displaced header location on the stack
movptr(Address(disp_hdr, 0), hdr);
if (os::is_MP()) MacroAssembler::lock(); // must be immediately before cmpxchg!
cmpxchgptr(disp_hdr, Address(obj, hdr_offset));
if (PrintBiasedLockingStatistics) {
cond_inc32(Assembler::equal,
ExternalAddress((address)BiasedLocking::fast_path_entry_count_addr()));
}
jcc(Assembler::equal, done);
subptr(hdr, rsp);
andptr(hdr, aligned_mask - os::vm_page_size());
movptr(Address(disp_hdr, 0), hdr);
//slow_case就是MonitorEnterStub
jcc(Assembler::notZero, slow_case);
// done
bind(done);
return null_check_offset;
}
其主要逻辑跟解释器的实现是基本一致的。