JUC学习笔记——共享模型之管程( 八 )

【JUC学习笔记——共享模型之管程】首先我们回忆一下对象头格式:
|--------------------------------------------------------------------|--------------------||Mark Word (64 bits)|State||--------------------------------------------------------------------|--------------------|| unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 |01|Normal||--------------------------------------------------------------------|--------------------|| thread:54 |epoch:2| unused:1 | age:4 | biased_lock:1 |01|Biased||--------------------------------------------------------------------|--------------------||ptr_to_lock_record:62|00| Lightweight Locked ||--------------------------------------------------------------------|--------------------||ptr_to_heavyweight_monitor:62|10| Heavyweight Locked ||--------------------------------------------------------------------|--------------------|||11|Marked for GC||--------------------------------------------------------------------|--------------------|我们进行简单解释:

  • Normal:正常锁
  • Biased:正常偏向锁
  • Lightweight Locked :轻量级锁
  • Heavyweight Locked:重量级锁
  • biased_lock : 控制偏向锁的开启的码位
我们针对偏向锁进行简单解释:
  • 如果开启了偏向锁(默认开启)那么对象创建后,markword值为0x05即最后3位为101,这时它的 thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效
  • 如果想避免延迟,可以加 VM 参数- XX:BiasedLockingStartupDelay=0来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001
  • 这时它的 hashcode、 age 都为 0,第一次用到 hashcode 时才会赋值
  • 但是如果调用了hashcode方法就会导致覆盖掉biased_lock关闭偏向锁
撤销偏向状态我们这里总结了三种撤销偏向状态的方法
撤销 - 调用对象 hashCode我们给出简单解释:
  • hashcode与biased_lock的字节码位置冲突,若调用hashcode方法得到hashcode就会覆盖掉biased_lock位置,导致偏向锁失效
  • 调用了对象的hashCode,但偏向锁的对象MarkWord中存储的是线程 id,如果调用hashCode会导致偏向锁被 撤销
  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode
撤销 - 其它线程使用对象我们给出简单解释:
  • 当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
我们给出简单示例:
/*主代码*/private static void test2() throws InterruptedException {Dog d = new Dog();Thread t1 = new Thread(() -> {synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}synchronized (TestBiased.class) {TestBiased.class.notify();}// 如果不用 wait/notify 使用 join 必须打开下面的注释// 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的/*try {System.in.read();} catch (IOException e) {e.printStackTrace();}*/}, "t1");t1.start();Thread t2 = new Thread(() -> {synchronized (TestBiased.class) {try {TestBiased.class.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}, "t2");t2.start();}/*结果*/[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001撤销 - 调用 wait/notify我们进行简单解释:
  • wait和notify方法都是重量级锁的专属方法,如果调用就会导致升级为重量级锁
我们给出简单代码示例:
/*主代码*/public static void main(String[] args) throws InterruptedException {Dog d = new Dog();Thread t1 = new Thread(() -> {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));try {d.wait();} catch (InterruptedException e) {e.printStackTrace();}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}}, "t1");t1.start();new Thread(() -> {try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (d) {log.debug("notify");d.notify();}}, "t2").start();}/*结果*/[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101[t2] - notify[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010批量重偏向我们先来介绍一下批量重偏向:

推荐阅读