并发编程(二十一)Synchronized锁升级-轻量级锁加锁与解锁

55 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第19天,点击查看活动详情

image.png

1.轻量级锁加锁与解锁

(1)轻量级锁加锁

线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间(Lock Record记录),并将对象头中的Mark Word(前30位 (25位的hashcode,4位的分代年龄,1位是否为偏向锁))复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针(指向线程栈帧里边的Lock Record的指针)。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

(2)轻量级锁解锁

轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word(Lock Record记录)替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

2.示例代码

public class LightLock {
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        System.out.println("====A 加锁前==="+ClassLayout.parseInstance(obj).toPrintable());
        Thread A = new Thread() {
            @SneakyThrows
            @Override
            public void run() {
                synchronized (obj) {
                    System.out.println("===A 加锁中==="+ClassLayout.parseInstance(obj).toPrintable());
                    Thread.sleep(2000);
                }

            }
        };
        A.start();
        Thread.sleep(500);
        System.out.println("====B加锁前==="+ClassLayout.parseInstance(obj).toPrintable());
        Thread B = new Thread() {
            @SneakyThrows
            @Override
            public void run() {
                synchronized (obj) {
                    System.out.println("====B加锁中==="+ClassLayout.parseInstance(obj).toPrintable());
                    Thread.sleep(1000);
                }
            }
        };
        B.start();
        Thread.sleep(5000);
        synchronized (obj) {
            System.out.println("====再次加锁中==="+ClassLayout.parseInstance(obj).toPrintable());
        }

        Object objnew = new Object();
        synchronized (objnew) {
            System.out.println("====新对象加锁中==="+ClassLayout.parseInstance(objnew).toPrintable());
        }
    }

}

注意:记着设置偏向锁延迟特别大,-XX:BiasedLockingStartupDelay=99999

image.png 执行效果如下:

====A 加锁前===java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

===A 加锁中===java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           90 ee 34 1a (10010000 11101110 00110100 00011010) (439676560)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

====B加锁前===java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           90 ee 34 1a (10010000 11101110 00110100 00011010) (439676560)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

====B加锁中===java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           1a 72 41 15 (00011010 01110010 01000001 00010101) (356610586)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

====再次加锁中===java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           1a 72 41 15 (00011010 01110010 01000001 00010101) (356610586)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

====新对象加锁中===java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           a0 f6 10 03 (10100000 11110110 00010000 00000011) (51443360)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

我们会发现,A加锁前是无锁状态,A加锁中是轻量级锁并且信息是记录的是指向锁记录的指针,B加锁前由于还没争抢还是保存的是轻量级锁并且记录的是指向锁记录的指针。B加锁中可以看到是重量级锁并且记录的是重量级锁的指针。当锁都释放完毕后,再次加锁这个对象,我们发现直接就是重量级锁!如果是新对象加锁的话是轻量级锁。

3.总结

真正的锁升级,是依赖于 当前new出来的对象的(轻量级锁升级为重量级锁)

轻量级锁升级为重量级锁:这个时候,只要我们的线程发生了竞争,并且CAS替换失败,就会发起锁膨胀,升级为重量级锁(针对的是一个对象实例)