synchronized锁升级那点事

synchronized的升级过程

在这里插入图片描述
引入JOL(Java Object Layout)来打印java对象头在内存中的字节码。

<dependency>
	<groupId>org.openjdk.jol</groupId>
	<artifactId>jol-core</artifactId>
	<version>0.10</version>
</dependency>

对象创建时

对象创建时的状态有两种:

  1. 无锁
  2. 匿名偏向锁

关闭了偏向锁或者在偏向延迟内创建的对象锁的状态为无锁(01)。

Object object = new Object();
System.out.println(ClassLayout.parseInstance(object).toPrintable());

object对应的对象结构如下:

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

可以用虚拟机参数-XX:-UseBiasedLocking关闭偏向锁,JDK8默认开启。

可以用虚拟机参数-XX:BiasedLockingStartupDelay来调整偏向延迟的时间,默认4000ms,设置为0,表示不延迟。

偏向延迟时间后创建的对象锁的状态为匿名偏向锁(101)。

TimeUnit.SECONDS.sleep(5);
Object object = new Object();
System.out.println(ClassLayout.parseInstance(object).toPrintable());

object对应的对象结构如下:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      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

为什么JVM默认开启偏向延迟?因为JVM虚拟机自己有一些默认启动的线程,里面有大量的同步代码,这些同步代码启动时就知道肯定会有竞争,如果使用偏向锁,就会造成偏向锁不断的进行锁撤销和锁升级的操作,效率较低。

无锁升级为轻量级锁

测试代码如下:

Object object = new Object();
System.out.println(ClassLayout.parseInstance(object).toPrintable());
synchronized (object) {
    
    
    System.out.println(ClassLayout.parseInstance(object).toPrintable());
}
System.out.println(ClassLayout.parseInstance(object).toPrintable());

object对应的对象结构如下:

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

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           48 e1 58 02 (01001000 11100001 01011000 00000010) (39379272)
      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)                           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

object对象刚创建出来时由于还在偏向延迟内,所以状态为无锁(01),当使用synchronized进行同步时,无锁(01)就会升级为轻量级锁(00),当锁释放后,锁的状态就会变为无锁(01)。

匿名偏向升级为偏向锁

测试代码如下

TimeUnit.SECONDS.sleep(5); // 也可以不休眠,设置偏向延迟时间为0,-XX:BiasedLockingStartupDelay=0
Object object = new Object();
System.out.println(ClassLayout.parseInstance(object).toPrintable());
synchronized (object) {
    
    
    System.out.println(ClassLayout.parseInstance(object).toPrintable());
}
System.out.println(ClassLayout.parseInstance(object).toPrintable());

object对应的对象结构如下:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      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)                           05 98 47 02 (00000101 10011000 01000111 00000010) (38246405)
      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)                           05 98 47 02 (00000101 10011000 01000111 00000010) (38246405)
      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

当开启了偏向锁,到达偏向延迟时间后创建出来的对象锁的状态为匿名偏向锁(001),当使用synchronized进行同步时,锁会偏向于第一次持有锁的线程,锁状态对应的标志位不变,但是对象头中会存储持有锁线程的ID,当锁释放后,锁的状态不变,对象头的结构也不会变,便于下次曾经持有锁的再次来获取锁,只需要判断线程ID是否是自己,无需再次偏向,效率高。

匿名偏向锁变为无锁

测试代码:

TimeUnit.SECONDS.sleep(5); // 也可以不休眠,设置偏向延迟时间为0,-XX:BiasedLockingStartupDelay=0
Object object = new Object();
System.out.println(ClassLayout.parseInstance(object).toPrintable());
object.hashCode();
System.out.println(ClassLayout.parseInstance(object).toPrintable());

object对应的对象结构如下:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      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)                           01 8f 3d 44 (00000001 10001111 00111101 01000100) (1144884993)
      4     4        (object header)                           61 00 00 00 (01100001 00000000 00000000 00000000) (97)
      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

当开启了偏向锁,到达偏向延迟时间后创建出来的对象锁的状态为匿名偏向锁(001),当计算对象的hashcode时,hashcode会覆盖偏向锁标志位的空间,所以锁的状态由匿名偏向锁(001)变为无锁(01)。

偏向锁升级为轻量级锁

轻量级锁也称为自旋锁,无锁,自适应锁。

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。

线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

当一个对象的锁状态为偏向锁时,另外一个线程来竞争锁,不管持有锁的线程的同步块有没有执行完,这把锁都会进行撤销。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程(Stop the World),然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,先将对象恢复成无锁状态,再将无锁升级为轻量级锁(遍历偏向锁线程的栈帧,如果里面还有LockRecord记录,说明还持有锁,那么让持有偏向锁的线程持有轻量级锁,否则让另一个线程持有轻量级锁),最后唤醒暂停的线程。

也就是偏向锁升级为轻量级锁的过程中,会涉及到偏向锁的撤销,锁的撤销会消耗系统资源,所以,在锁竞争特别激烈的时候,用偏向锁未必效率高,还不如直接使用轻量级锁。这也是为什么jvm偏向锁的默认设置要延迟4s,因为在jvm启动过程中会开启多个线程,有大量的锁竞争的操作。

偏向锁升级为轻量级锁的代码演示:

TimeUnit.SECONDS.sleep(5); // 休眠5s,保证偏向锁开启
Object object = new Object();
Thread t = new Thread(() -> {
    
    
    synchronized (object) {
    
    
    }
});
t.start();
t.join(); // 等待t1执行完
System.out.println(ClassLayout.parseInstance(object).toPrintable());
synchronized (object) {
    
    
    System.out.println(ClassLayout.parseInstance(object).toPrintable());
}
System.out.println(ClassLayout.parseInstance(object).toPrintable());

运行结果如下:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 b8 8f 1d (00000101 10111000 10001111 00011101) (495958021)
      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)                           e0 e7 90 02 (11100000 11100111 10010000 00000010) (43050976)
      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)                           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

从上面的运行结果可以发现对象头的锁状态由偏向锁(101)变成了轻量级锁(00),最后锁释放后变成了无锁(01)。

偏向锁升级为重量级锁

测试代码如下:

TimeUnit.SECONDS.sleep(5); // 休眠5s,保证偏向锁开启

Object object = new Object();

System.out.println(ClassLayout.parseInstance(object).toPrintable()); // 101

Thread t = new Thread(() -> {
    
    
    synchronized (object) {
    
    
        System.out.println(ClassLayout.parseInstance(object).toPrintable()); // 101
        try {
    
    
            object.wait(2000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(ClassLayout.parseInstance(object).toPrintable()); // 10
    }
});
t.start();
t.join();
TimeUnit.SECONDS.sleep(3); // 等待锁释放
System.out.println(ClassLayout.parseInstance(object).toPrintable()); // 01

运行结果如下:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      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)                           05 20 a2 1e (00000101 00100000 10100010 00011110) (513941509)
      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 b2 06 1c (00011010 10110010 00000110 00011100) (470200858)
      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)                           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

总结,当调用对象的wait()方法锁的状态将会直接从偏向锁(101)直接进入重量级锁(10)。

轻量级锁升级为重量级锁

在jdk1.6以前,轻量级锁自旋超过10次或者自旋的线程数超过CPU核数的一半,就会升级为重量级锁,如果太多线程自旋,自旋就是个CAS获取锁的死循环,会导致CPU消耗过大,不如升级为重量级锁,进入等待队列(不消耗CPU),轻量级锁可以使用-XX:PreBlockSpin来开启。JDK6中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。

自适应自旋锁意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

升级重量级需要向操作系统申请资源,CPU从用户态切换会内核态用,线程挂起,进入等待队列,等待操作系统的调度,然后再映射回用户空间。

测试代码如下:

Object object = new Object();

Thread t1 = new Thread(() -> {
    
    
    synchronized (object) {
    
    
        System.out.println("thread name: " + Thread.currentThread().getName());
        System.out.println(ClassLayout.parseInstance(object).toPrintable()); // 00
        try {
    
    
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}, "t1");

Thread t2 = new Thread(() -> {
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(2); // 等待t1获取到锁,造成资源竞争
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    synchronized (object) {
    
    
        System.out.println("thread name: " + Thread.currentThread().getName());
        System.out.println(ClassLayout.parseInstance(object).toPrintable()); // 10
    }
}, "t2");

t1.start();
t2.start();
t1.join();
t2.join();

TimeUnit.SECONDS.sleep(10); // 休眠一会,锁的释放没那么快
System.out.println(ClassLayout.parseInstance(object).toPrintable()); // 01

运行结果如下:

thread name: t1
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           08 ef ea 1e (00001000 11101111 11101010 00011110) (518713096)
      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

thread name: t2
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           5a b2 f7 1b (01011010 10110010 11110111 00011011) (469217882)
      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)                           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

当t1持有轻量级锁的过程中,t2过来获取锁,造成资源的竞争,就会使得轻量级锁(00)膨胀为重量级锁(10),重量级锁释放后,锁的状态变回无锁。

猜你喜欢

转载自blog.csdn.net/u022812849/article/details/108489531