在JDK6中,HotSpot虚拟机对锁进行了大量的优化。众多的锁优化技术包括自旋锁、锁消除、锁粗化、轻量级锁、偏向锁等。
自旋锁
- JDK6以前,Java虚拟机的锁都是通过互斥来实现的。互斥同步对性能最大的影响是线程的阻塞,线程的阻塞和唤醒需要CPU从用户态转为核心态。
- 挂起和恢复线程会增加CPU负担,影响并发能力。
- 同时,很多情况下数据的锁定状态只会持续很短的一段时间,为此挂起和恢复线程并不值得。
自旋锁的机制是:
- 让线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。
- 为了让线程等待,让线程执行一个忙循环(自旋)
自旋锁在JDK1.4.2就已经引入,但默认是关闭的;在JDK6中就已经改为默认开启了
- 自旋等待不能代替阻塞
- 自旋等待本身要占用处理器时间
- 如果锁被占用的时间很短,自旋等待的效果就会非常好
- 反之,自旋的线程只会消耗处理器资源,而不做任何有价值的工作
- 因此自旋等待的时间必须有一定的限度
- 自旋次数的默认值是十次,用户也可以使用参数
-XX:PreBlockSpin
来自行更改
- 自旋次数的默认值是十次,用户也可以使用参数
适应性自旋
- 在JDK6中对自旋锁的优化,引入了自适应的自旋
- 自旋的时间不再是固定的了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定
锁消除
锁消除指虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除。
锁粗化
- 原则上,我们在编写代码时,应该将同步块的作用范围限制的尽量小,即只在共享数据的部分进行同步,这样可以减少同步的操作
- 但是如果对一个对象反复加锁和解锁(如加锁操作出现在循环体中),也会带来不必要的性能损耗。
锁粗化的机制是:如果虚拟机探测到一系列操作都是对同一个对象加锁,则会把加锁同步的范围粗化到这一系列操作之外。
如:
public static void test() {
List<String> list = new ArrayList<>();
//虽然synchronized是在循环里面,但实际上加锁的范围会扩大到循环外
for (int i=0; i<10; i++) {
synchronized (Demo.class) {
list.add(i + "");
}
}
System.out.println(list);
}
轻量级锁
轻量级锁是JDK6引入的新型锁机制,区别于传统的重量级锁
- 轻量级锁并不是用来代替重量级锁的
- 它的设计初衷是在没有多线程竞争的前提下,减少传统重量级锁使用操作系统互斥量产生的性能消耗
轻量级锁的设计依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”
- 如果没有竞争,轻量级锁便通过CAS操作成功避免了使用互斥量的开销
- 但如果存在竞争,除了互斥量本身的开销外,还额外发生了CAS操作的开销
- 因此在有竞争的情况下,轻量级锁比重量级锁更慢
轻量级锁的工作流程
- 在代码即将进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(
Lock Record
)的空间,用于存储锁对象目前的Mark Word
的拷贝 - 虚拟机将使用CAS操作尝试把对象的
Mark Word
更新为指向Lock Record
的指针- 如果这个操作成功了,即代表该线程拥有了这个对象的锁,并且对象
Mark Word
的锁标志位将转变为“00”,表示此对象出于轻量级锁定状态 - 如果这个操作失败了,那么意味着至少存在一个线程与当前线程竞争获取该对象的锁
- 虚拟机首先会检查对象的
Mark Word
是否指向当前线程的栈帧- 如果是,说明当前线程已经拥有了这个对象的锁,直接进入同步代码块执行
- 否则说明这个锁对象已经被其他线程抢占了
- 如果两个以上的线程争用同一个锁,则轻量级锁升级为重量级锁
- 锁标志的状态值变为“10”
- Mark Word中存储的是指向重量级锁(互斥量)的指针
- 后面等待的线程必须进入阻塞状态
- 虚拟机首先会检查对象的
- 如果这个操作成功了,即代表该线程拥有了这个对象的锁,并且对象
偏向锁
偏向锁是JDK6引入的锁优化措施,目的是消除数据在无竞争情况下的同步原语,进一步提高性能。
偏向锁的机制是:这个锁会偏向第一个获得锁的线程,如果接下来该锁一直没有被其他线程获取,则持有偏向锁的线程永远不需要同步。
偏向锁的工作流程
- 假设当前虚拟机启用了偏向锁(启用参数
-XX:+UserBiased Locking
,JDK6起HotSpot虚拟机的默认值),那么当所对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设置为“01”,把偏向模式设置为“1”,表示进入偏向模式 - 同时使用CAS操作把获取这个锁的线程的ID记录在对象的
Mark Word
之中- 如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作
- 一旦另一个线程去尝试获取这个锁,偏向模式就会结束
- 根据锁对象目前是否处于被锁定的状态决定是否撤销偏向(偏向模式设置为“0”)
- 撤销后标志位恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)状态
- 后续操作按照轻量级锁去执行
参考文献:《深入理解Java虚拟机》