大家好,我是烤鸭:
前几天看马士兵老师的并发的课,里边讲到了 synchronize 锁的膨胀过程,今天想用代码演示一下。
关于synchronize jdk 1.5 以后的优化,由重量级锁调整为膨胀过程。分别是 偏向锁 轻量级锁(自旋锁) 重量级锁。
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();
}
}
关于对象布局,我们就先不研究了,这里重点说一下 对象头。
线程[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
看下面的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
}
有很多文章对 synchronize 分析过,我这里只是想使用代码演示一下各种场景,很多地方并没有深入到源码和原理层面。
简单来说,就是:
无锁:mark word 记录 hashcode和分代年龄。
单线程加锁(偏向锁),mark word 记录线程id。
偏向锁升级到 轻量级锁,mark word 值 替换为 当前线程栈中的lock record 的指针。
轻量级锁到重量级锁:mark word 值 重量级锁 的指针。
其中自旋锁是 轻量级锁到重量级锁 发生的: