从Java SE 1.6开始,为了减少获得锁和释放锁带来的性能消耗,就引入了轻量级锁。轻量级锁在对象内存布局中 MarkWord 锁标志位为 00,它可以由偏向锁对象因存在多个线程访问而升级成轻量级锁,当然,轻量级锁也可能因多个线程同时访问同步代码块升级成重量级锁。
轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。
假设有两个方法同步块,利用同一个对象加锁:
static final Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块 A
method2();
}
}
public static void method2() {
synchronized( obj ) {
// 同步块 B
}
}
1.创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构.内部可以存储锁定对象的Mark Word;
2.如果cas替换成功,对象头存储了锁记录地址和状态00,表示由该线程给对象加锁.如下图:
3.如果cas失败,有两种情况:
- 如果是其他线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程;
- 如果是自己执行了synchronized锁重入,那么再添加一条Lock Record作为重入的计数;
4.当退出synchronized代码块如果有取值为null的锁记录,表示有重入,这时重置锁记录,表示重入计数减一
5.当退出synchronized代码块的锁记录不为null,这时使用cas将Mark Word的值恢复给对象头
- 成功,解锁成功;
失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程;
锁膨胀
如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其他线程为此线程加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁;
1.当Thread1进行轻量级加锁时,Thread0已经对该对象加了轻量级锁
2.这时Thread1加轻量级锁失败,进入锁膨胀流程;即为Object对象申请Monitor锁,让Object指向重量级锁地址;
- 然后自己进入Monitor的EntryList BLOCKED
3.当Thread0退出同步块解锁时,使用cas将Mark Word的值恢复给对象头,失败.这时会进入重量级锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中的BLOCKED线程;
重量级锁
内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。