前言
由于 Monitor 基于操作系统调用,上下文切换导致开销大,在竞争不激烈时性能不算很好, 在 jdk6 之后进了系列优化。前文对优化措施进行了简单介绍,下面将一一介绍这些优化的细节,行文思路大致如下:
- 从重量级锁的优化开始讲,一是自旋锁,二是尽量避免进入 Monitor ,即使用轻量级锁
- 讲解轻量级锁及加锁解锁流程
- 轻量级锁在没有竞争时,每次重入仍然需要执行cas操作,为解决这个问题,因而产生了偏向锁
- 详细介绍偏向锁
Synchronized 锁的细节
一、自旋锁
自旋锁比较简单,逻辑在上篇也已经进行过阐述,这一篇章我们着重看下它的性能如何?
在竞争度较小的时候,重量级锁的上下文切换导致的开销相对于 CPU 处理任务的时间占比较重,此种情况下,自旋锁的性能有优势,因自旋而导致的 CPU 浪费在可接受范围内;当竞争激烈的时候,继续使用自旋锁则得不偿失,性能上比直接使用重量级锁要差,大量的等待锁的时间被浪费。
根据任务处理时间不同,自旋锁表现也不一,在任务持续时间长的情况下,自旋太久显然是对 CPU 时间片的浪费,且因任务持续时间长,在 10 此默认自旋次数的情况下,易出现自旋结束也无法获取到锁,那么此次空转就是毫无收益的性能浪费。在任务处理时间较短的情况下,显然自旋获得锁的几率要大,因此如果对要执行的任务有很明确的处理时长认知,可以根据情况适当的调整初始自旋次数,JVM 参数为:-XX:PreBlockSpin。
二、轻量级锁
根据观察,多线程中并不总是存在着竞争,使用轻量级锁避免了锁 Monitor 这繁重的数据结构,轻量级锁通常只锁一个字段(锁记录),在 HotSpot 中的实现是在当前线程的栈帧中创建锁记录结构(Lock Record)。
1.轻量级锁加锁流程
- 在当前线程的栈帧中创建 Lock Record
- 构建一个无锁状态的 Displaced Mark Word
- 将 Displaced Mark Word 存储到 Lock Record 中的 _displaced_header 属性
- CAS 更新 Displaced Mark Word 指针,注意【3】是将 Lock record 的 header 的值设置成一个 displaced mark word,【4】这一步是将当前对象头的 Mark Word 中的高30 位(全文都是只针对 32 位虚拟机来谈)指向 Lock Record 中的 header。
4.1 CAS 成功,执行同步代码块
4.2 CAS 失败,存在两种情况
3.2.1 判断是否为锁重入(关于轻量级锁的可重入有疑问,见下文)
3.2.2 锁被其他线程占有,需要竞争锁,进入锁膨胀过程 - 加锁成功的话,当前对象的 Mark Word 后两位锁标志位置为 00,余下高位作为指针存储 Lock Record 的地址
轻量级锁加锁源码如下:
// traditional lightweight locking if (!success) { // markOop就是对象头结构, 生成对象头,这个对象头的状态设置为无锁,