从 class 文件 看 synchronize 锁膨胀过程(偏向锁 轻量级锁 自旋锁 重量级锁)

大家好,我是烤鸭:

    前几天看马士兵老师的并发的课,里边讲到了 synchronize 锁的膨胀过程,今天想用代码演示一下。

1.  简单介绍

    关于synchronize jdk 1.5 以后的优化,由重量级锁调整为膨胀过程。分别是 偏向锁 轻量级锁(自旋锁) 重量级锁。

2.  用例编写

pom文件增加 jol的包,用于看对象头的信息。



    org.openjdk.jol
    jol-core
    0.10

下面的注释已经写的挺清楚的了,关于锁几种状态的转换。 

SyncSourceTest.java

package src.source;

import org.openjdk.jol.info.ClassLayout;

/**
 * Created by test on 2020/5/10
 */
public class SyncSourceTest {
    static Object noLock;
    static Object biaseLock;
    static Object lightLock;
    static Object heavyLock;
    public static void main(String[] args) throws InterruptedException {
        noLock = new Object();
        // 无锁状态,由于print 方法是synchronize 修饰,其实打印语句就已经是加偏向锁了(如果满足下面的偏向锁条件)
        System.out.print("线程["+Thread.currentThread().getName()+"]:无锁状态对象布局:"+ClassLayout.parseInstance(noLock).toPrintable());
        // 偏向锁,由于JVM 默认偏向锁4s后开启,可以线程sleep.5 或者设置VM参数关闭延迟 -XX:BiasedLockingStartupDelay=0
        Thread.sleep(5000L);
        biaseLock = new Object();
        System.out.println("线程["+Thread.currentThread().getName()+"]:偏向锁状态对象布局:"+ClassLayout.parseInstance(biaseLock).toPrintable());
        // 轻量级锁,由于轻量级锁是偏向锁升级的,需要先给对象一个偏向锁,如果不加偏向锁,只有一个线程加锁变成偏向锁
        lightLock = new Object();
        synchronized (lightLock) {
            System.out.println("线程["+Thread.currentThread().getName()+"]:[轻量级锁提前加偏向锁]轻量级锁状态对象布局:"+ClassLayout.parseInstance(lightLock).toPrintable());
        }
        synLight();

        // 重量级锁
        heavyLock = new Object();
        synHeavy();
    }

    public static void synLight() throws InterruptedException {
        for (int i = 0; i < 1; i++) {
            getLightLock();
        }
    }

    public static void getLightLock() {
        new Thread(() -> {
            try {
                synchronized (lightLock){
                    System.out.println("线程["+Thread.currentThread().getName()+"]:轻量级锁状态对象布局:"+ClassLayout.parseInstance(lightLock).toPrintable());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }

    public static void synHeavy() throws InterruptedException {
        for (int i = 0; i < 2; i++) {
            getHeavyLock();
        }
    }

    private static void getHeavyLock() {
        new Thread(() -> {
            try {
                synchronized (heavyLock){
                    System.out.println("线程["+Thread.currentThread().getName()+"]:重量级锁状态对象布局:"+ClassLayout.parseInstance(heavyLock).toPrintable());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
}

关于对象布局,我们就先不研究了,这里重点说一下 对象头

HotSpot 虚拟机的对象头( Object Header )分为两部分,第一 部分用于存储对象自身的运行时数据,如哈希码(HashCode )、 GC 分代年龄( Generational GC Age ) 等。这部分数据的长度在32 位和 64 位的 Java 虚拟机中分别会占用 32 个或 64 个比特,官方称它为 Mark Word ”。这部分是实现 轻量级锁和偏向锁 的关键。另外一部分用于存储指向方法区对象类型数据的指 针,如果是数组对象,还会有一个额外的部分用于存储数组长度。
从 class 文件 看 synchronize 锁膨胀过程(偏向锁 轻量级锁 自旋锁 重量级锁)_第1张图片
 
运行之后我们看一下输出信息:
线程[main]:无锁状态对象布局: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
线程[main]:偏向锁状态对象布局: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

线程[main]:[轻量级锁提前加偏向锁]轻量级锁状态对象布局:java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 28 e3 02 (00000101 00101000 11100011 00000010) (48441349)
      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-2]:重量级锁状态对象布局:java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           ba ee 0d 26 (10111010 11101110 00001101 00100110) (638447290)
      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-0]:轻量级锁状态对象布局:java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           88 f5 a4 29 (10001000 11110101 10100100 00101001) (698676616)
      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-1]:重量级锁状态对象布局:java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           ba ee 0d 26 (10111010 11101110 00001101 00100110) (638447290)
      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


Process finished with exit code 0

可以看出 无锁状态下各个状态下 mark word 后三位的值:

无锁:001

偏向:101

轻量级锁:000

重量级锁:010

本来想写一下,关于 轻量级锁 当前线程栈帧中 lock record 和 mark word的变化,无奈查了很多资料不知道在哪可以看到 lock record,有的说是显式或者隐式地创建lock record 空间,咱也不清楚了。更多关于轻量级锁的源码看这篇吧。

https://blog.csdn.net/z69183787/article/details/104502540?utm_source=app

3. 编译class文件

看下面的class文件,顺便说一下jvm的字节码指令。

可以看下 class文件里边对象的变化:

 7: putstatic     #3                  // Field noLock:Ljava/lang/Object; 静态变量 初始化时

39: getstatic     #3                  // Field noLock:Ljava/lang/Object; 获取 静态变量

而到了偏向锁对象初始化之前,线程 睡眠了5秒。

57: ldc2_w        #16                   // long 5000l ,5000入栈
60: invokestatic  #18                 // Method java/lang/Thread.sleep:(J)V , 执行 sleep

135: monitorenter 对应的这行代码 :synchronized (lightLock)

184: monitorexit  加锁结束

190: monitorexit  后面又有一次 加锁结束

原因是 线程内部加锁后,调用 print 方法,又加了一次锁(重入锁),所以需要释放两次。

D:\gitee\rep\leetcode-gradle\src\main\java\src\source> javac -classpath ".;D:\dev\repository\org\openjdk\jol\jol-core\0.10\jol-core-0.10.jar"  -encoding UTF-8 .\SyncSourceTest.java

D:\gitee\rep\leetcode-gradle\src\main\java\src\source> javap -c .\SyncSourceTest.class
Compiled from "SyncSourceTest.java"
public class src.source.SyncSourceTest {
  static java.lang.Object noLock;

  static java.lang.Object biaseLock;

  static java.lang.Object lightLock;

  static java.lang.Object heavyLock;

  public src.source.SyncSourceTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.InterruptedException;
    Code:
       0: new           #2                  // class java/lang/Object
       3: dup
       4: invokespecial #1                  // Method java/lang/Object."":()V
       7: putstatic     #3                  // Field noLock:Ljava/lang/Object;
      10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      13: new           #5                  // class java/lang/StringBuilder
      16: dup
      17: invokespecial #6                  // Method java/lang/StringBuilder."":()V
      20: ldc           #7                  // String 线程[
      22: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      25: invokestatic  #9                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      28: invokevirtual #10                 // Method java/lang/Thread.getName:()Ljava/lang/String;
      31: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      34: ldc           #11                 // String ]:无锁状态对象布局:
      36: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      39: getstatic     #3                  // Field noLock:Ljava/lang/Object;
      42: invokestatic  #12                 // Method org/openjdk/jol/info/ClassLayout.parseInstance:(Ljava/lang/Object;)Lorg/openjdk/jol/info/ClassLayout;
      45: invokevirtual #13                 // Method org/openjdk/jol/info/ClassLayout.toPrintable:()Ljava/lang/String;
      48: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      51: invokevirtual #14                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      54: invokevirtual #15                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
      57: ldc2_w        #16                 // long 5000l
      60: invokestatic  #18                 // Method java/lang/Thread.sleep:(J)V
      63: new           #2                  // class java/lang/Object
      66: dup
      67: invokespecial #1                  // Method java/lang/Object."":()V
      70: putstatic     #19                 // Field biaseLock:Ljava/lang/Object;
      73: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      76: new           #5                  // class java/lang/StringBuilder
      79: dup
      80: invokespecial #6                  // Method java/lang/StringBuilder."":()V
      83: ldc           #7                  // String 线程[
      85: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      88: invokestatic  #9                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      91: invokevirtual #10                 // Method java/lang/Thread.getName:()Ljava/lang/String;
      94: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      97: ldc           #20                 // String ]:偏向锁状态对象布局:
      99: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     102: getstatic     #19                 // Field biaseLock:Ljava/lang/Object;
     105: invokestatic  #12                 // Method org/openjdk/jol/info/ClassLayout.parseInstance:(Ljava/lang/Object;)Lorg/openjdk/jol/info/ClassLayout;
     108: invokevirtual #13                 // Method org/openjdk/jol/info/ClassLayout.toPrintable:()Ljava/lang/String;
     111: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     114: invokevirtual #14                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     117: invokevirtual #21                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     120: new           #2                  // class java/lang/Object
     123: dup
     124: invokespecial #1                  // Method java/lang/Object."":()V
     127: putstatic     #22                 // Field lightLock:Ljava/lang/Object;
     130: getstatic     #22                 // Field lightLock:Ljava/lang/Object;
     133: dup
     134: astore_1
     135: monitorenter
     136: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
     139: new           #5                  // class java/lang/StringBuilder
     142: dup
     143: invokespecial #6                  // Method java/lang/StringBuilder."":()V
     146: ldc           #7                  // String 线程[
     148: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     151: invokestatic  #9                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
     154: invokevirtual #10                 // Method java/lang/Thread.getName:()Ljava/lang/String;
     157: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     160: ldc           #23                 // String ]:[轻量级锁提前加偏向锁]轻量级锁状态对象布局:
     162: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     165: getstatic     #22                 // Field lightLock:Ljava/lang/Object;
     168: invokestatic  #12                 // Method org/openjdk/jol/info/ClassLayout.parseInstance:(Ljava/lang/Object;)Lorg/openjdk/jol/info/ClassLayout;
     171: invokevirtual #13                 // Method org/openjdk/jol/info/ClassLayout.toPrintable:()Ljava/lang/String;
     174: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     177: invokevirtual #14                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     180: invokevirtual #21                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     183: aload_1
     184: monitorexit
     185: goto          193
     188: astore_2
     189: aload_1
     190: monitorexit
     191: aload_2
     192: athrow
     193: invokestatic  #24                 // Method synLight:()V
     196: new           #2                  // class java/lang/Object
     199: dup
     200: invokespecial #1                  // Method java/lang/Object."":()V
     203: putstatic     #25                 // Field heavyLock:Ljava/lang/Object;
     206: invokestatic  #26                 // Method synHeavy:()V
     209: return
    Exception table:
       from    to  target type
         136   185   188   any
         188   191   188   any

  public static void synLight() throws java.lang.InterruptedException;
    Code:
       0: iconst_0
       1: istore_0
       2: iload_0
       3: iconst_1
       4: if_icmpge     16
       7: invokestatic  #27                 // Method getLightLock:()V
      10: iinc          0, 1
      13: goto          2
      16: return

  public static void getLightLock();
    Code:
       0: new           #28                 // class java/lang/Thread
       3: dup
       4: invokedynamic #29,  0             // InvokeDynamic #0:run:()Ljava/lang/Runnable;
       9: invokespecial #30                 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V
      12: invokevirtual #31                 // Method java/lang/Thread.start:()V
      15: return

  public static void synHeavy() throws java.lang.InterruptedException;
    Code:
       0: iconst_0
       1: istore_0
       2: iload_0
       3: iconst_2
       4: if_icmpge     16
       7: invokestatic  #32                 // Method getHeavyLock:()V
      10: iinc          0, 1
      13: goto          2
      16: return
}

4.  总结

有很多文章对 synchronize 分析过,我这里只是想使用代码演示一下各种场景,很多地方并没有深入到源码和原理层面。

简单来说,就是:

无锁:mark word 记录 hashcode和分代年龄。

单线程加锁(偏向锁),mark word 记录线程id。

偏向锁升级到 轻量级锁,mark word 值 替换为 当前线程栈中的lock record 的指针。

轻量级锁到重量级锁:mark word 值 重量级锁 的指针。

其中自旋锁是 轻量级锁到重量级锁 发生的:

CAS 操作尝试把对象的 Mark Word 更新为指向 Lock Record 的指针,如果成功了,就是轻量级锁,失败了,就变成重量级锁。

 

你可能感兴趣的:(JAVA,并发编程)