对象头布局
Synchronized的锁信息放在Java对象头的MarkWord里
32位虚拟机MarkWord结构
64位虚拟机MarkWord结构
Synchronized通过对象头中的MarkWord后三位来标识锁的类型。
Synchronized在JVM中的实现原理
重量级锁对应的锁标志位是10,存储了指向重量级监视器锁的指针,在Hotspot中,对象的监视器(monitor)锁对象由ObjectMonitor对象实现(C++),其跟同步相关的数据结构如下:
ObjectMonitor() {
_count = 0; //用来记录该对象被线程获取锁的次数
_waiters = 0;
_recursions = 0; //锁的重入次数
_owner = NULL; //指向持有ObjectMonitor对象的线程
_WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表
}
其线程的运行过程如下图
线程的生命周期存在5个状态,start、running、waiting、blocking和dead
- 当多个线程同时访问该方法,那么这些线程会先被放进_EntryList队列,此时线程处于blocking状态
- 当一个线程获取到了实例对象的监视器(monitor)锁,那么就可以进入running状态,执行方法,此时,ObjectMonitor对象的_owner指向当前线程,_count加1表示当前对象锁被一个线程获取
- 当running状态的线程调用wait()方法,那么当前线程释放monitor对象,进入waiting状态,ObjectMonitor对象的_owner变为null,_count减1,同时线程进入_WaitSet队列,直到有线程调用notify()方法唤醒该线程,则该线程会先进入Enterlist 然后竞争,重新获取monitor对象进入_Owner区
- 如果当前线程执行完毕,那么也释放monitor对象,进入terminated状态,ObjectMonitor对象的_owner变为null,_count减1
Synchronized在字节码层面实现原理
Synchronized修饰代码块
采用monitorenter,monitorexit
指令,同步方法块在进入代码块时插入了monitorentry语句,在退出代码块时插入了monitorexit语句。
例如,同步代码块如下:
public class SyncCodeBlock {
public int i;
public void syncTask(){
synchronized (this){
i++;
}
}
}
对同步代码块编译后的class字节码文件反编译,结果如下(仅保留方法部分的反编译内容):
public void syncTask();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter //注意此处,进入同步方法
4: aload_0
5: dup
6: getfield #2 // Field i:I
9: iconst_1
10: iadd
11: putfield #2 // Field i:I
14: aload_1
15: monitorexit //注意此处,退出同步方法
16: goto 24
19: astore_2
20: aload_1
21: monitorexit //注意此处,退出同步方法
22: aload_2
23: athrow
24: return
Exception table:
//省略其他字节码.......
可以看出同步方法块在进入代码块时插入了monitorentry语句,在退出代码块时插入了monitorexit语句,为了保证不论是正常执行完毕(第15行)还是异常跳出代码块(第21行)都能执行monitorexit语句,因此会出现两句monitorexit语句。
Synchronized修饰方法块
Synchronized方法同步不再是通过插入monitorentry和monitorexit指令实现,而是由方法调用指令来读取运行时常量池中的ACC_SYNCHRONIZED标志隐式实现的,如果方法表结构(method_info Structure)中的ACC_SYNCHRONIZED标志被设置,那么线程在执行方法前会先去获取对象的monitor对象,如果获取成功则执行方法代码,执行完毕后释放monitor对象,如果monitor对象已经被其它线程获取,那么当前线程被阻塞。
例如同步方法代码如下:
public class SyncMethod {
public int i;
public synchronized void syncTask(){
i++;
}
}
对同步方法编译后的class字节码反编译,结果如下(仅保留方法部分的反编译内容):
public synchronized void syncTask();
descriptor: ()V
//方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: return
LineNumberTable:
line 12: 0
line 13: 10
}
可以看出方法开始和结束的地方都没有出现monitorentry和monitorexit指令,但是出现的ACC_SYNCHRONIZED标志位。说明当Synchronized修饰的是方法时,通过ACC_SYNCHRONIZED
实现
Synchronized在汇编层面的实现原理
采用 lock cmpxchg
指令,多核CPU需要加lock
前缀,非多核被替换为nop
。关于compxchg指令详解