聊聊 Java 中绕不开的 Synchronized 关键字-二

Java并发

synchronized 的一些学习记录

jdk1.6 以后对 synchronized 进行了一些优化,包括偏向锁,轻量级锁,重量级锁等

这些锁的加锁方式大多跟对象头有关,我们可以查看 jdk 代码

首先对象头的位置注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Bit-format of an object header (most significant first, big endian layout below):
//
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
// size:64 ----------------------------------------------------->| (CMS free block)
//
// unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
// JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
// narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
// unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
1
2
3
4
5
6
7
enum { locked_value             = 0,
unlocked_value = 1,
monitor_value = 2,
marked_value = 3,
biased_lock_pattern = 5
};

我们可以用 java jol库来查看对象头,通过一段简单的代码来看下

1
2
3
4
5
6
public class ObjectHeaderDemo {
public static void main(String[] args) throws InterruptedException {
L l = new L();
System.out.println(ClassLayout.parseInstance(l).toPrintable());
}
}

Untitled

然后可以看到打印输出,当然这里因为对齐方式,我们看到的其实顺序是反过来的,按最后三位去看,我们这是 001,好像偏向锁都没开,这里使用的是 jdk1.8,默认开始偏向锁的,其实这里有涉及到了一个配置,jdk1.8 中偏向锁会延迟 4 秒开启,可以通过添加启动参数 -XX:+PrintFlagsFinal,看到

偏向锁延迟

因为在初始化的时候防止线程竞争有大量的偏向锁撤销升级,所以会延迟 4s 开启

我们再来延迟 5s 看看

1
2
3
4
5
6
7
public class ObjectHeaderDemo {
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
L l = new L();
System.out.println(ClassLayout.parseInstance(l).toPrintable());
}
}

https://img.nicksxs.com/uPic/2LBKpX.jpg

可以看到偏向锁设置已经开启了,我们来是一下加个偏向锁

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ObjectHeaderDemo {
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
L l = new L();
System.out.println(ClassLayout.parseInstance(l).toPrintable());
synchronized (l) {
System.out.println("1\n" + ClassLayout.parseInstance(l).toPrintable());
}
synchronized (l) {
System.out.println("2\n" + ClassLayout.parseInstance(l).toPrintable());
}
}
}

看下运行结果

https://img.nicksxs.com/uPic/V2l78m.png

可以看到是加上了 101 = 5 也就是偏向锁,后面是线程 id

当我再使用一个线程来竞争这个锁的时候

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
public class ObjectHeaderDemo {
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
L l = new L();
System.out.println(ClassLayout.parseInstance(l).toPrintable());
synchronized (l) {
System.out.println("1\n" + ClassLayout.parseInstance(l).toPrintable());
}
Thread thread1 = new Thread() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (l) {
System.out.println("thread1 获取锁成功");
System.out.println(ClassLayout.parseInstance(l).toPrintable());
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread1.start();
}
}

https://img.nicksxs.com/uPic/bRMvlR.png

可以看到变成了轻量级锁,在线程没有争抢,只是进行了切换,就会使用轻量级锁,当两个线程在竞争了,就又会升级成重量级锁

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
public class ObjectHeaderDemo {
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
L l = new L();
System.out.println(ClassLayout.parseInstance(l).toPrintable());
synchronized (l) {
System.out.println("1\n" + ClassLayout.parseInstance(l).toPrintable());
}
Thread thread1 = new Thread() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (l) {
System.out.println("thread1 获取锁成功");
System.out.println(ClassLayout.parseInstance(l).toPrintable());
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (l) {
System.out.println("thread2 获取锁成功");
System.out.println(ClassLayout.parseInstance(l).toPrintable());
}
}
};
thread1.start();
thread2.start();
}
}

class L {
private boolean myboolean = true;
}

https://img.nicksxs.com/uPic/LMzMtR.png

可以看到变成了重量级锁。