从Java SE 1.6开始,为了减少获得锁和释放锁带来的性能消耗,就引入了轻量级锁。轻量级锁在对象内存布局中 MarkWord 锁标志位为 00,它可以由偏向锁对象因存在多个线程访问升级轻量级锁,当然,轻量级锁也可能因多个线程同时访问同步代码块升级成重量级锁。
轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。
假设有两个方法同步块,利用同一个对象加锁:

  1. static final Object obj = new Object();
  2. public static void method1() {
  3. synchronized( obj ) {
  4. // 同步块 A
  5. method2();
  6. }
  7. }
  8. public static void method2() {
  9. synchronized( obj ) {
  10. // 同步块 B
  11. }
  12. }

1.创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构.内部可以存储锁定对象的Mark Word;
轻量级锁、锁膨胀、重量级锁 - 图1
2.如果cas替换成功,对象头存储了锁记录地址和状态00,表示由该线程给对象加锁.如下图:
轻量级锁、锁膨胀、重量级锁 - 图2
3.如果cas失败,有两种情况:

  • 如果是其他线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程;
  • 如果是自己执行了synchronized锁重入,那么再添加一条Lock Record作为重入的计数;

轻量级锁、锁膨胀、重量级锁 - 图3
4.当退出synchronized代码块如果有取值为null的锁记录,表示有重入,这时重置锁记录,表示重入计数减一
image.png
5.当退出synchronized代码块的锁记录不为null,这时使用cas将Mark Word的值恢复给对象头

  • 成功,解锁成功;
  • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程;

    锁膨胀

    如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其他线程为此线程加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁;
    1.当Thread1进行轻量级加锁时,Thread0已经对该对象加了轻量级锁
    image.png
    2.这时Thread1加轻量级锁失败,进入锁膨胀流程;

  • 即为Object对象申请Monitor锁,让Object指向重量级锁地址;

  • 然后自己进入Monitor的EntryList BLOCKED

image.png
3.当Thread0退出同步块解锁时,使用cas将Mark Word的值恢复给对象头,失败.这时会进入重量级锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中的BLOCKED线程;

重量级锁

内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。