Java锁升级、偏向锁、轻量级锁

偏向锁

当锁对象第一次被线程获取时,虚拟机会把对象头的锁状态标志设置为01(即偏向状态),同时,使用CAS操作把获取到这个锁的线程的ID记录在对象头的mark word中。如果这个CAS操作成功,那么,以后持有这个偏向锁的线程在进入这个锁相关的同步块的时候都不需要进行同步,只需要测试对象头的mark word中是否存储着当前线程的ID。若测试成功,则直接进入同步块。若测试失败,则需要测试mark word中锁状态标志位是否为01。若不为01(即表示对象此时处于轻量级/重量级锁状态),则使用CAS操作竞争锁。若为01(即01表示对象此时处于已偏向状态),则尝试使用CAS将对象头的偏向线程ID改为当前线程ID。

此处,着重讨论mark word中锁状态标志位为01的情况:由于偏向锁采用了一种等到竞争出现才会释放锁的机制(即偏向锁不会主动释放),所以当前线程就会知道锁对象此时(可能)处于竞争状态了。(在全局安全点——没有正在执行的字节码)JVM会首先暂停持有偏向锁的线程,然后检查拥有偏向锁的线程是否存活。若没有存活,则将对象头设置为无锁状态并重新偏向。若线程仍然存活,则持有偏向锁的栈会被执行,遍历偏向锁的锁记录、栈中的锁记录以及对象头的mark word(就是检查持有偏向锁的线程是否还需要持有偏向锁)。若检查结果表明该持有偏向锁的线程不需要持有偏向锁了,则将锁对象恢复为无锁状态并重新偏向。否则表明此时锁对象真的处于竞争状态了,那么偏向锁就会被升级成轻量级锁。
注意:当锁对象开启偏向时mark word中有一个比特位将会被置为1,所以偏向状态和未锁定状态除了其他字段的区别外,在这个比特位上也有区别。

轻量级锁

在代码进入同步块的时候,如果此同步对象没有被锁定,即锁标志位为01,那么JVM会先在当前线程的栈帧中创建一个存储锁对象头中Mark Word拷贝的空间(称为锁记录),同时将锁对象头中的Mark Word复制到锁记录中并加上Displaced前缀(Displaced Mark Word)。

然后,JVM使用CAS将锁对象头中的Mark Word更新为指向锁记录的指针。若更新成功,则当前线程便持有了该对象的锁,并且锁对象的标志位变为00(表示此对象处于轻量级锁定状态)。若更新操作失败了,虚拟机会首先检查对象的Mark Word是否指向当前线程的栈帧,如果是则表明当前线程已经拥有了这个对象的锁,可以直接进入同步块继续执行。如果不是,则当前线程进入自旋,(个人见解,不当之处欢迎指正)则会有两种情况会膨胀为重量级锁:

  • 当前线程自旋期间,有第三个线程来获取锁,则膨胀为重量级锁,标志位被当前线程置为10,Mark Word中存储的就是指向重量级锁(互斥量)的指针,当前线程和第三个线程进入阻塞状态;
  • 当前线程自旋超过限定次数,则膨胀为重量级锁,标志位被当前线程置为10,Mark Word中存储的就是指向重量级锁(互斥量)的指针,当前线程进入阻塞状态。

如果当前线程自旋的时候没有发生上面两种情况,那么最终它将获得锁对象的轻量级锁。
轻量级锁的解锁也是通过CAS操作进行的,如果对象的Mark Word仍指向线程的锁记录,那么就用CAS把对象当前的Mark Word和线程中的Displaced Mark Word替换回来。若替换成功,则整个同步过程完成,若替换失败,则表明有其他线程竞争过锁,就要在释放锁的同时唤醒被挂起的线程。

参考文献:

1、《深入理解Java虚拟机》,周志明著;

2、《Java并发编程的艺术》,方腾飞,魏鹏,程晓明著;

你可能感兴趣的:(Java学习笔记)