分析 Synchronized 锁的4个状态
1.前言
通过JOL查看对象的头信息,Synchronized 关键字锁的状态:无锁、偏向锁、轻量级锁、重量级锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_lock()返回时,该互斥锁已被锁定。线程调用该函数让互斥锁上锁,如果该互斥锁已被另一个线程锁定和拥有,则调用该线程将阻塞,直到该互斥锁变为可用为止。 对于 Solaris线程,请参见mutex_lock 语法
- 会阻塞
我们可以从JVM 源码中看到对glibc库调用,操作系统层级的锁
ThreadCritical::ThreadCritical
1 | ThreadCritical::ThreadCritical() { |
这个是底层系统库,我们是看不到源码的,只能自己下载。可以在glibc/nptl/phread_mutex_lock.c里看到实现。
2.验证准备
- 先通过一个jni调用来获取pthread层面的tid
JniTool.java
这里jnitool必须在 java.library.path这一jvm变量所指向的路径中,1
2
3
4
5
6
7
8package jnitool;
public class JniTool {
static {
System.loadLibrary("jnitools");
}
public native void getThreadID();
}
可以通过如下方法获得该变量System.getProperty(“java.library.path”);
生成.h文件,javah -jni jnitool.JniTool
打印java线程的pid和tid,jvm层面
1 |
|
使用make编译
1 | vim makefile |
对象头信息 object header 关闭指针压缩 -XX:-UseCompressedOops
关于对象头信息的详情介绍,可以
自己写个工具方法,根据markworld转换出头部信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15|--------------------------------------------------------------------------------------------------------------|
| Object Header (128 bits) |
|--------------------------------------------------------------------------------------------------------------|
| Mark Word (64 bits) | Klass Word (64 bits) |
|--------------------------------------------------------------------------------------------------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 无锁
|----------------------------------------------------------------------|--------|------------------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
| ptr_to_lock_record:62 | lock:2 | OOP to metadata object | 轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | OOP to metadata object | 重量锁
|----------------------------------------------------------------------|--------|------------------------------|
| | lock:2 | OOP to metadata object | GC
|--------------------------------------------------------------------------------------------------------------|接受JOL输出,自己写一个转换工具,方便debug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56public void parseHeaderInfo(String printable) {
String[] split = printable.replaceAll("[|)]", "").split("\n");
StringBuilder builder = new StringBuilder();
for (int i = 2; i <= 3; i++) {
builder.append(split[i].split("\\(")[2]);
}
ArrayList<String> strings = new ArrayList<>(Arrays.asList(builder.toString().split(" ")));
Collections.reverse(strings);
builder = new StringBuilder();
for (String string : strings) {
builder.append(string);
}
String markWord = builder.toString();
System.out.println("\n--------START-----------");
//System.out.println("mark world:"+markWord);
String lock = markWord.substring(62);
String biased_lock = String.valueOf(markWord.charAt(61));
builder = new StringBuilder();
if (lock.equals("01")) {
if (biased_lock.equals("0")) {
System.out.println("--------无锁-----------");
System.out.println("unused:25:" + markWord.substring(0, 25));
System.out.println("identity_hashcode:31: " + markWord.substring(25, 56)
+ " (Hex:" + Integer.toHexString(Integer.parseInt(markWord.substring(25, 56), 2)) + ")");
System.out.println("unused:1:" + markWord.substring(56, 57));
System.out.println("age:4:" + markWord.substring(57, 61));
System.out.println("biased_lock:1:" + markWord.substring(61, 62));
System.out.println("lock:2:" + markWord.substring(62));
} else {
System.out.println("--------偏向锁--------");
System.out.println("thread:54:" + markWord.substring(0, 54)
+"(Dex:"+Long.parseLong(markWord.substring(0, 54), 2)+")");
System.out.println("epoch:2:" + markWord.substring(54, 56));
System.out.println("unused:1:" + markWord.substring(56, 57));
System.out.println("age:4:" + markWord.substring(57, 61));
System.out.println("biased_lock:1:" + markWord.substring(61, 62));
}
} else {
String hexString = Long.toHexString(Long.parseLong(markWord.substring(0, 62), 2));
if (lock.equals("00")) {
System.out.println("--------轻量级锁--------");
System.out.println("ptr_to_lock_record:62:" + markWord.substring(0, 62)
+ " (Hex:" + hexString + ")");
System.out.println("lock:2:" + markWord.substring(62));
} else if (lock.equals("10")) {
System.out.println("--------重量级锁--------");
System.out.println("ptr_to_heavyweight_monitor::62:" + markWord.substring(0, 62)
+ " (Hex:" + hexString + ")");
System.out.println("lock:2:" + markWord.substring(62));
} else if (lock.equals("11")) {
System.out.println("--------GC标记--------");
}
}
System.out.println("--------END-----------\n");
}
3.无锁
JVM直接优化,进行无锁访问,虽然加了锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51import jnitools.JniTools;
import org.openjdk.jol.info.ClassLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class SyncTest {
final Object lock = new Object();
JniTools jniTool = new JniTools();
public static void main(String[] args) throws InterruptedException {
SyncTest test = new SyncTest();
test.parseHeaderInfo((ClassLayout.parseInstance(test.lock).toPrintable()));
System.out.println("---------------------------start--------------------");
test.start();
}
public void start() throws InterruptedException {
Runnable runnable = new Runnable() {
int count = 0;
public void run() {
while (true) {
if (count < 100) {
count++;
} else {
return;
}
try {
lock();
parseHeaderInfo(ClassLayout.parseInstance(lock).toPrintable());
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
new Thread(runnable).start();
Thread.sleep(5000);
new Thread(runnable).start();
}
public void lock() {
synchronized (lock) {
jniTool.getTid();
}
}
}分析,虽然有多个线程访问,但是不存在,同时竞争。
log 无锁
out.log1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46--------START-----------
--------无锁-----------
unused:25:0000000000000000000000000
identity_hashcode:31: 0000000000000000000000000000000 (Hex:0)
unused:1:0
age:4:0000
biased_lock:1:0
lock:2:01
--------END-----------
---------------------------start--------------------
java_lock log-----------PID = 189 TID = 139775743776512
--------START-----------
--------无锁-----------
unused:25:0000000000000000000000000
identity_hashcode:31: 0000000000000000000000000000000 (Hex:0)
unused:1:0
age:4:0000
biased_lock:1:0
lock:2:01
--------END-----------
java_lock log-----------PID = 189 TID = 139775743776512
--------START-----------
--------无锁-----------
unused:25:0000000000000000000000000
identity_hashcode:31: 0000000000000000000000000000000 (Hex:0)
unused:1:0
age:4:0000
biased_lock:1:0
lock:2:01
--------END-----------
java_lock log-----------PID = 189 TID = 139775743776512
--------START-----------
--------无锁-----------
unused:25:0000000000000000000000000
identity_hashcode:31: 0000000000000000000000000000000 (Hex:0)
unused:1:0
age:4:0001
biased_lock:1:0
lock:2:01
--------END-----------
4 偏向锁
- 当关闭偏向锁功能时
- 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁。
- 一开始就是偏向锁,处于可偏向,谁先来锁,就偏向谁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53import jnitools.JniTools;
import org.openjdk.jol.info.ClassLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class SyncTest {
final Object lock = new Object();
JniTools jniTool = new JniTools();
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
SyncTest test = new SyncTest();
test.parseHeaderInfo((ClassLayout.parseInstance(test.lock).toPrintable()));
System.out.println("---------------------------start--------------------");
test.start();
Thread.sleep(50000);
}
public void start() throws InterruptedException {
Runnable runnable = new Runnable() {
public void run() {
int count = 0;
while (true) {
if (count < 50) {
count++;
} else {
System.out.println("Thread "+Thread.currentThread().getName()+" return ");
return;
}
try {
lock();
parseHeaderInfo(ClassLayout.parseInstance(lock).toPrintable());
// Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
new Thread(runnable).start();
Thread.sleep(5000);
new Thread(runnable).start();
}
public void lock() {
synchronized (lock) {
jniTool.getTid();
}
}
}
Thread.sleep(5000); 主要在线程启动时候加上了sleep
为了减少JVM启动时初始化时间,JVM默认延时加载偏向锁。大概为4s左右,具体时间因机器而异。当然我们也可以设置JVM参数 -XX:BiasedLockingStartupDelay=0 来取消延时加载偏向锁
你会发现占用 thread 和 epoch 的 位置的均为0,说明当前偏向锁并没有偏向任何线程,此时这个偏向锁正处于可偏向状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31--------START-----------
--------偏向锁--------
thread:54:000000000000000000000000000000000000000000000000000000(Dex:0)
epoch:2:00
unused:1:0
age:4:0000
biased_lock:1:1
--------END-----------
---------------------------start--------------------
java_lock log-----------PID = 398 TID = 140630884325120
--------START-----------
--------偏向锁--------
thread:54:000000000000000001111111111001110100010000100001101100(Dex:137335212140)
epoch:2:00
unused:1:0
age:4:0001
biased_lock:1:1
--------END-----------
java_lock log-----------PID = 398 TID = 140630884325120
--------START-----------
--------偏向锁--------
thread:54:000000000000000001111111111001110100010000100001101100(Dex:137335212140)
epoch:2:00
unused:1:0
age:4:0001
biased_lock:1:1
--------END-----------
Thread Thread-1 return 当线程2开始的时候,线程1已结结束了,这里怀疑线程复用,因为这里不满足线程重新偏向,且线程没有重新偏向
偏向的线程没有变化,且pthread的TID的对象头的里的信息都没有变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22java_lock log-----------PID = 398 TID = 140630884325120
--------START-----------
--------偏向锁--------
thread:54:000000000000000001111111111001110100010000100001101100(Dex:137335212140)
epoch:2:00
unused:1:0
age:4:0001
biased_lock:1:1
--------END-----------
Thread Thread-1 return
java_lock log-----------PID = 398 TID = 140630884325120
--------START-----------
--------偏向锁--------
thread:54:000000000000000001111111111001110100010000100001101100(Dex:137335212140)
epoch:2:00
unused:1:0
age:4:0001
biased_lock:1:1
--------END-----------
5 轻量级锁
我们让线程1放锁后,不让线程1退出
让线程2去拿锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import jnitool.JniTool;
import org.openjdk.jol.info.ClassLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class SyncTest {
final Object lock = new Object();
JniTool jniTool = new JniTool();
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
SyncTest test = new SyncTest();
test.parseHeaderInfo((ClassLayout.parseInstance(test.lock).toPrintable()));
System.out.println("---------------------------start--------------------");
test.start();
}
public void start() throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
int count = 0;
System.out.println(Thread.currentThread().getName() + " t1 start");
while (true) {
if (count < 50) {
count++;
} else {
return;
}
lock();
}
}
};
new Thread(runnable).start();
Thread.sleep(1000);//这里保证退出
new Thread(runnable).start();
}
public void lock() {
synchronized (lock) {
Class<?> aClass = lock.getClass();
jniTool.getThreadID();
parseHeaderInfo(ClassLayout.parseInstance(lock).toPrintable());
}
}
}线程1执行的时候,是偏向了线程1的
线程1退出,线程2来的时候,直接升级了轻量级锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21java_lock log PID = 61919 TID = 123145379807232
--------START-----------Thread-1
--------偏向锁--------
thread:54:000000000000000001111111110000101010110100010011100100(Dex:137181742308)
epoch:2:00
unused:1:0
age:4:0000
biased_lock:1:1
--------END-----------Thread-1
Thread-2 t1 start
java_lock log PID = 61919 TID = 123145379807232
--------START-----------Thread-2
--------轻量级锁--------
ptr_to_lock_record:62:00000000000000000111000000000000000001001001111001111000001110 (Hex:1c0001279e0e)
lock:2:00
--------END-----------Thread-2
java_lock log PID = 61919 TID = 123145379807232
6. 重量级锁
偏向锁直接升级
2个线程,同时并发访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46import jnitool.JniTool;
import org.openjdk.jol.info.ClassLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class SyncTest {
final Object lock = new Object();
JniTool jniTool = new JniTool();
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
SyncTest test = new SyncTest();
test.parseHeaderInfo((ClassLayout.parseInstance(test.lock).toPrintable()));
System.out.println("---------------------------start--------------------");
test.start();
}
public void start() throws InterruptedException {
Runnable runnable = new Runnable() {
public void run() {
int count = 0;
System.out.println(Thread.currentThread().getName() + " t1 start");
while (true) {
if (count < 50) {
count++;
} else {
return;
}
lock();
}
}
};
new Thread(runnable).start();
new Thread(runnable).start();
}
public void lock() {
synchronized (lock) {
Class<?> aClass = lock.getClass();
jniTool.getThreadID();
parseHeaderInfo(ClassLayout.parseInstance(lock).toPrintable());
}
}
}从可偏向状态,直接升级为重量级锁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19--------START-----------main
--------偏向锁--------
thread:54:000000000000000000000000000000000000000000000000000000(Dex:0)
epoch:2:00
unused:1:0
age:4:0000
biased_lock:1:1
--------END-----------main
---------------------------start--------------------
Thread-1 t1 start
Thread-2 t1 start
java_lock log PID = 61884 TID = 123145554096128
--------START-----------Thread-1
--------重量级锁--------
ptr_to_heavyweight_monitor::62:00000000000000000111111110101101000111010000000110001011010010 (Hex:1feb474062d2)
lock:2:10
--------END-----------Thread-1
总结
- 初始状态可能是偏向锁的可偏向状态,也可能是无锁状态
- 初始锁,若只有一个线程连续的多次访问,构成锁的偏向
- 不构成锁的重新偏向情况下,后面有多个线程轮流访问,不构成锁的竞争,锁升级到轻量级锁
- 可以有连续状态,连续锁的升级。