0%

synchronized实现中的锁膨胀

JVM在不同的竞争情况下,对synchronized提供了不同的优化方式

  • 锁消除
  • 自旋
  • 自适应自旋

在优化过程中锁主要存在4种状态:

  1. 无锁状态
  2. 偏向锁状态
  3. 轻量级锁状态
  4. 重量级锁状态

这四种状态会根据资源竞争情况进行膨胀(升级)。

偏向锁

偏向锁的目的是为了消除无竞争情况下的同步原语,从而进一步提升性能。‘偏’指的是该锁会偏向于首先获得他的线程,如果在执行过程中,该锁未被其他的线程获取,则持有偏向锁的线程就一直不需要同步。

获取偏向锁的过程
  1. 检查Mark World偏向锁是否打开。若为1,则执行步骤2,否则代表不支持偏向锁。
  2. 检查Mark World储存的线程ID是否为当前线程ID,如果是则执行同步块,否则执行步骤3.
  3. 使用CAS操作将Mark World中线程ID改为当前线程ID,若成功则执行同步代码块,否则执行步骤4。
  4. 当拥有该锁的线程到达安全点后,挂起该线程,升级为轻量级锁。
释放偏向锁的过程
  1. 偏向锁采取了竞争才会释放锁的方案,线程并不会主动放弃偏向锁,需要等待其他线程竞争。
  2. 等待全局安全点。
  3. 暂停拥有偏向锁的线程,检查持有偏向锁的线程是否活着,如果处于非活动状态,则将对象头设置为无锁状态,否则设置为被锁定状态。如果锁对象处于无锁状态,则恢复到无锁状态(01),以允许其他线程竞争,如果锁对象处于锁定状态,则挂起持有偏向锁的线程,并将对象头Mark World的锁记录指针改为当前线程的锁记录,锁升级为轻量锁。

轻量级锁

轻量级锁区别于使用mutex方式实现的重量级锁,他使用对象头中的Mark World进行同步。

获取轻量锁的过程
  1. 在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。
  2. 拷贝对象头中的Mark Word复制到锁记录中。
  3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤4,否则执行步骤5。
  4. 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态。
  5. 如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。
释放轻量锁的过程
  1. 使用CAS操作将对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来(依据Mark Word中锁记录指针是否还指向本线程的锁记录),如果替换成功,则执行步骤2,否则执行步骤3。
  2. 如果替换成功,整个同步过程就完成了,恢复到无锁的状态(01)。
  3. 如果替换失败,说明有其他线程尝试获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。

该过程的完整流程图

image