Java 高并发编程 三(共享模型之管程)_ synchronized底层原理、优化Monitor重量级锁、轻量级锁、自旋锁(优化重量级锁竞争)、偏向锁·

一、 Monitor 概念·

1、 Java 对象头 (重点)·

对象头包含两部分:运行时元数据(Mark Word)和类型指针 (Klass Word)

  1. 运行时元数据
  • 哈希值(HashCode),可以看作是堆中对象的地址
  • GC分代年龄(年龄计数器) (用于新生代from/to区晋升老年代的标准, 阈值为15)
  • 锁状态标志 (用于JDK1.6对synchronized的优化 -> 轻量级锁)
  • 线程持有的锁
  • 偏向线程ID (用于JDK1.6对synchronized的优化 -> 偏向锁)
  • 偏向时间戳
  1. 类型指针
  • 指向类元数据InstanceKlass,确定该对象所属的类型。指向的其实是方法区中存放的类元信息

说明:如果对象是数组,还需要记录数组的长度


  • 以 32 位虚拟机为例,普通对象的对象头结构如下,其中的Klass Word为类型指针,指向方法区对应的Class对象;
  • image.png
  • 数组对象
  • image.png
  • 其中 Mark Word 结构为: 无锁(001)、偏向锁(101)、轻量级锁(00)、重量级锁(10)
  • image.png
  • 所以一个对象的结构如下:
  • image.png

2. Monitor 原理 (Synchronized底层实现-重量级锁)·

多线程同时访问临界区: 使用重量级锁

  • JDK6对Synchronized的优先状态:偏向锁–>轻量级锁–>重量级锁
  • Monitor被翻译为监视器或者管程
  • 每个Java对象都可以关联一个(操作系统的)Monitor,如果使用synchronized给对象上锁(重量级),该对象头的MarkWord中就被设置为指向Monitor对象的指针

下图原理解释: 当Thread1访问到synchronized(obj)中的共享资源的时候

  • 首先会将synchronized中的锁对象中对象头的MarkWord去尝试指向操作系统的Monitor对象. 让锁对象中的MarkWord和Monitor对象相关联. 如果关联成功, 将obj对象头中的MarkWord的对象状态从01改为10。
  • 因为Monitor没有和其他的obj的MarkWord相关联, 所以Thread1就成为了该Monitor的Owner(所有者)。
  • 又来了个Thread1执行synchronized(obj)代码, 它首先会看看能不能执行该临界区的代码; 它会检查obj是否关联了Montior, 此时已经有关联了, 它就会去看看该Montior有没有所有者(Owner), 发现有所有者了(Thread2); Thread1也会和该Monitor关联, 该线程就会进入到它的EntryList(阻塞队列);
  • 当Thread2执行完临界区代码后, Monitor的Owner(所有者)就空出来了. 此时就会通知Monitor中的EntryList阻塞队列中的线程, 这些线程通过竞争, 成为新的所有者

image.png


image.png

  • 刚开始时Monitor中的Owner为null
  • 当Thread-2 执行synchronized(obj){}代码时就会将Monitor的所有者Owner 设置为 Thread-2,上锁成功,Monitor中同一时刻只能有一个Owner
  • 当Thread-2 占据锁时,如果线程Thread-3,Thread-4也来执行synchronized(obj){}代码,就会进入EntryList中变成BLOCKED状态
  • Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争时是非公平的 (仍然是抢占式)
  • 图中 WaitSet 中的Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲wait-notify 时会分析

注意:

  • synchronized 必须是进入同一个锁对象的monitor 才有上述的效果; —> 也就要使用同一把锁
  • 不加 synchronized的锁对象不会关联监视器(不会看对象的markword,不会检查monitor),不遵从以上规则
  • image.png
  • 它加锁就是依赖底层操作系统的 mutex相关指令实现, 所以会造成用户态和内核态之间的切换, 非常耗性能 !
  • 在JDK6的时候, 对synchronized进行了优化, 引入了轻量级锁, 偏向锁, 它们是在JVM的层面上进行加锁逻辑, 就没有了切换的消耗~

3、synchronized原理·

1
2
3
4
5
6
7
8
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock) {
counter++;
}
}

反编译后的部分字节码image.png 注意:方法级别的 synchronized 不会在字节码指令中有所体现

4、synchronized 原理进阶·

小故事 image.pngimage.png image.png

5、轻量级锁 (用于优化Monitor这类的重量级锁)·

通过锁记录的方式, 场景 : 多个线程交替进入临界区

  • 轻量级锁的使用场景: 如果一个对象虽然有多个线程要对它进行加锁,但是加锁的时间是错开的(也就是没有人可以竞争的),那么可以使用轻量级锁来进行优化。
  • 轻量级锁对使用者是透明的,即语法仍然是synchronized (jdk6对synchronized的优化),假设有两个方法同步块,利用同一个对象加锁
  • eg: 线程A来操作临界区的资源, 给资源加锁,到执行完临界区代码,释放锁的过程, 没有线程来竞争, 此时就可以使用轻量级锁; 如果这期间有线程来竞争的话, 就会升级为重量级锁(synchronized)
1
2
3
4
5
6
7
8
9
10
11
12
13
static final Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块 A
method2();
}
}
public static void method2() {
synchronized( obj ) {
// 同步块 B
}
}

  • 每次指向到synchronized代码块时,都会在栈帧中创建锁记录(Lock Record JVM层面)对象,每个线程都会包括一个锁记录的结构,锁记录内部可以储存对象的MarkWord和锁对象引用reference(对象的引用地址)

image.png image.png image.png

  • 让锁记录中的Object reference指向锁对象地址,并且尝试用CAS(compare and sweep)将栈帧中的锁记录的(lock record 地址 00)替换Object对象的Mark Word,将Mark Word 的值(01)存入锁记录(lock record地址)中 ------相互替换
    • 01 表示 无锁 (看Mark Word结构, 数字的含义)
    • 00表示 轻量级锁

image.png

  • 如果cas替换成功, 获得了轻量级锁,那么对象的对象头储存的就是锁记录的地址和状态00,如下所示
    • 线程中锁记录, 记录了锁对象的锁状态标志; 锁对象的对象头中存储了锁记录的地址和状态, 标志哪个线程获得了锁
    • 此时栈帧中存储了对象的对象头中的锁状态标志,年龄计数器,哈希值等; 对象的对象头中就存储了栈帧中锁记录的地址和状态00, 这样的话对象就知道了是哪个线程锁住自己。

image.png

  • 如果cas替换失败,有两种情况 : ① 锁膨胀 ② 重入锁失败
    • 如果是其它线程已经持有了该Object的轻量级锁,那么表示有竞争,将进入 锁膨胀阶段
      • 此时对象Object对象头中已经存储了别的线程的锁记录地址 00,指向了其他线程;
    • 如果是自己的线程已经执行了synchronized进行加锁,那么再添加一条 Lock Record 作为重入锁的计数 – 线程多次加锁, 锁重入
      • 在上面代码中,临界区中又调用了method2, method2中又进行了一次synchronized加锁操作, 此时就会在虚拟机栈中再开辟一个method2方法对应的栈帧(栈顶), 该栈帧中又会存在一个独立的Lock Record, 此时它发现对象的对象头中指向的就是自己线程中栈帧的锁记录; 加锁也就失败了. 这种现象就叫做锁重入; 线程中有多少个锁记录, 就能表明该线程对这个对象加了几次锁 (锁重入计数)

image.png 轻量级锁解锁流程 :

  • 当线程退出synchronized代码块的时候,如果获取的是取值为 null 的锁记录 ,表示有锁重入,这时重置(移除)锁记录,表示重入计数减一

image.png

  • 当线程退出synchronized代码块的时候,如果获取的锁记录取值不为 null,那么使用cas将Mark Word的值恢复给对象, 将直接替换的内容还原。
    • 成功则解锁成功 (轻量级锁解锁成功)
    • 失败,表示有竞争, 则说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程 (Monitor流程)

6、锁膨胀·

  • 如果在尝试加轻量级锁的过程中,cas替换操作无法成功,这是有一种情况就是其它线程已经为这个对象加上了轻量级锁,这是就要进行锁膨胀(有竞争),将轻量级锁变成重量级锁。
  • 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁, 此时发生锁膨胀

image.png

  • 这时Thread-1加轻量级锁失败,进入锁膨胀流程
    • 因为Thread-1线程加轻量级锁失败, 轻量级锁没有阻塞队列的概念, 所以此时就要为对象申请Monitor锁(重量级锁),让Object指向重量级锁地址 10,然后自己进入Monitor 的EntryList 变成BLOCKED状态

image.png

  • 当Thread-0 线程执行完synchronized同步块时,使用cas将Mark Word的值恢复给对象头, 肯定恢复失败,因为对象的对象头中存储的是重量级锁的地址,状态变为10了之前的是00, 肯定恢复失败。那么会进入重量级锁的解锁过程,即按照Monitor的地址找到Monitor对象,将Owner设置为null,唤醒EntryList中的Thread-1线程

7、自旋锁优化 (优化重量级锁竞争)·

  • 当发生重量级锁竞争的时候,还可以使用自旋来进行优化 (不加入Monitor的阻塞队列EntryList中),如果当前线程自旋成功(即在自旋的时候持锁的线程释放了锁) ,那么当前线程就可以不用进行上下文切换(持锁线程执行完synchronized同步块后,释放锁,Owner为空,唤醒阻塞队列来竞争,胜出的线程得到cpu执行权的过程) 就获得了锁
  • 优化的点: 不用将线程加入到阻塞队列, 减少cpu切换.

自旋重试成功的情况:image.png 自旋重试失败的情况,自旋了一定次数还是没有等到 持锁的线程释放锁, 线程2就会加入Monitor的阻塞队列(EntryList) image.png 注意:

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。Java 7 之后不能控制是否开启自旋功能

8、偏向锁 (biased lock) (用于优化轻量级锁重入)·

场景: 没有竞争的时候, 一个线程中多次使用synchronized需要重入加锁的情况; (只有一个线程进入临界区)

  • 在经常需要竞争的情况下就不使用偏向锁, 因为偏向锁是默认开启的, 我们可以通过JVM的配置, 将偏向锁给关闭
  • 将进入临界区的线程的ID, 直接设置给锁对象的Mark word, 下次该线程又获取锁, 发现线程ID是自己, 就不需要CAS了
  • 在轻量级的锁中,我们可以发现,如果同一个线程对同一个对象进行重入锁时,也需要执行CAS替换操作,这是有点耗时。
  • 那么java6开始引入了偏向锁,将进入临界区的线程的ID, 直接设置给锁对象的Mark word, 下次该线程又获取锁, 发现线程ID是自己, 就不需要CAS了
    • 升级为轻量级锁的情况 (会进行偏向锁撤销) : 获取偏向锁的时候, 发现线程ID不是自己的, 此时通过CAS替换操作, 操作成功了, 此时该线程就获得了锁对象。( 此时是交替访问临界区, 撤销偏向锁, 升级为轻量级锁)
    • 升级为重量级锁的情况 (会进行偏向锁撤销) : 获取偏向锁的时候, 发现线程ID不是自己的, 此时通过CAS替换操作, 操作失败了, 此时说明发生了锁竞争。( 此时是多线程访问临界区, 撤销偏向锁, 升级为重量级锁)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test {
static final Object obj = new Object();

public static void m1() {
synchronized (obj) {
// 同步块A
m2();
}
}

public static void m2() {
synchronized (obj) {
// 同步块B
m3();
}
}

public static void m3() {
synchronized (obj) {
// 同步块C
}
}
}

image.png image.png image.png image.png


8.1、偏向锁状态 (了解)·

  • 运行时元数据(Mark Word)的结构如下:

image.png

  • Normal:一般状态,没有加任何锁,前面62位保存的是对象的信息,最后2位为状态(01),倒数第三位表示是否使用偏向锁(未使用:0)
  • Biased:偏向状态,使用偏向锁,前面54位保存的当前线程的ID,最后2位为状态(01),倒数第三位表示是否使用偏向锁(使用:1)
  • Lightweight:使用轻量级锁,前62位保存的是锁记录的指针,最后2位为状态(00)
  • Heavyweight:使用重量级锁,前62位保存的是Monitor的地址指针,最后2位为状态(10)

image.png

  • 如果开启了偏向锁(默认开启),在创建对象时,对象的Mark Word后三位应该是101
  • 但是偏向锁默认是有延迟的,不会再程序一启动就生效,而是会在程序运行一段时间(几秒之后),才会对创建的对象设置为偏向状态
  • 如果没有开启偏向锁,对象的Mark Word后三位应该是001

一个对象的创建过程·

  • 如果开启了偏向锁(默认是开启的),那么对象刚创建之后,Mark Word 最后三位的值101,并且这是它的ThreadId,epoch,age(年龄计数器)都是0,在加锁的时候进行设置这些的值.
  • 偏向锁默认是延迟的,不会在程序启动的时候立刻生效,如果想避免延迟,可以添加虚拟机参数来禁用延迟:-XX:BiasedLockingStartupDelay=0来禁用延迟

注意 : 处于偏向锁的对象解锁后,线程id(此处的线程ID不是Java中的线程id哦,这里的是操作系统分配的id)仍存储于对象头中 image.png 输出结果:

image.png

  • 测试禁用偏向锁:如果没有开启偏向锁,那么对象创建后最后三位的值为001,这时候它的hashcode,age都为0,hashcode是第一次用到hashcode时才赋值的。在上面测试代码运行时在添加 VM 参数-XX:-UseBiasedLocking禁用偏向锁(禁用偏向锁则优先使用轻量级锁),退出synchronized状态变回 001
    • 禁止偏向锁, 虚拟机参数-XX:-UseBiasedLocking; 优先使用轻量级锁
    • 输出结果: 最开始状态为001,然后加轻量级锁变成00,最后恢复成001

image.png

8.2、撤销偏向锁-hashcode方法 (了解)·

image.png

  • 测试 hashCode:当调用对象的hashcode方法的时候就会撤销这个对象的偏向锁,因为使用偏向锁时没有位置存hashcode的值了

8.3、撤销偏向锁-发生锁竞争 (升级为重量级锁)·

小故事: 线程A门上刻了名字, 但此时线程B也要来使用房间了, 所以要将偏向锁升级为轻量级锁. (线程B要在线程A使用完房间之后(执行完synchronized代码块),再来使用; 否则就成了竞争获取锁对象, 此时就要升级为重量级锁了)

偏向锁、轻量级锁的使用条件, 都是在于多个线程没有对同一个对象进行锁竞争的前提下, 如果有锁竞争,此时就使用重量级锁。

  • 这里我们演示的是偏向锁撤销, 变成轻量级锁的过程,那么就得满足轻量级锁的使用条件,就是没有线程对同一个对象进行锁竞争,我们使用wait 和 notify 来辅助实现
  • 虚拟机参数-XX:BiasedLockingStartupDelay=0确保我们的程序最开始使用了偏向锁

image.png

  • 输出结果,最开始使用的是偏向锁但是第二个线程尝试获取对象锁时(前提是: 线程一已经释放掉锁了,也就是执行完synchroized代码块) ,发现本来对象偏向的是线程一,那么偏向锁就会失效,加的就是轻量级锁

image.png

8.4、撤销偏向锁 - 调用 wait/notify (只有重量级锁才支持这两个方法)·

(调用wait方法会导致锁膨胀而使用重量级锁)

  • 会使对象锁变成重量级锁,因为wait/notify方法之后重量级锁才支持

9、批量重偏向·

  • 如果对象被多个线程访问,但是没有竞争 (上面撤销偏向锁就是这种情况: 一个线程执行完, 另一个线程再来执行, 没有竞争), 这时偏向T1的对象仍有机会重新偏向T2

    • 重偏向会重置Thread ID
  • 当撤销偏向锁101 升级为 轻量级锁00超过20次后(超过阈值),JVM会觉得是不是偏向错了,这时会在给对象加锁时,重新偏向至加锁线程 (T2)。

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
private static void test3() throws InterruptedException {
Vector<Dog> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("===============> ");
for (int i = 0; i < 30; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t2");
t2.start();
}

输出

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - ===============>
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101

9.1、批量撤销偏向锁·

  • 当 撤销偏向锁的阈值超过40以后 ,就会将整个类的对象都改为不可偏向的,新建的对象也是不可偏向的
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
static Thread t1,t2,t3;
private static void test4() throws InterruptedException {
Vector<Dog> list = new Vector<>();
int loopNumber = 39;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
LockSupport.unpark(t3);
}, "t2");
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t3");
t3.start();
t3.join();
log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}

9.2、同步省略 (锁消除)·

  1. 线程同步的代价是相当高的,同步的后果是降低并发性和性能
  2. 在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。
  3. 如果没有,那么JIT 编译器在编译这个同步块的时候就会取消对这部分代码的同步。这样就能大大提高并发性和性能。这个取消同步的过程就叫同步省略,也叫锁消除
  • 例如下面的智障代码,根本起不到锁的作用
1
2
3
4
5
6
7
public void f() {
Object hellis = new Object();
synchronized(hellis) {
System.out.println(hellis);
}
}

  • 代码中对hellis这个对象加锁,但是hellis对象的生命周期只在f()方法中,并不会被其他线程所访问到,所以在JIT编译阶段就会被优化掉,优化成:
1
2
3
4
5
public void f() {
Object hellis = new Object();
System.out.println(hellis);
}

字节码分析

  • 代码
1
2
3
4
5
6
7
public void f() {
Object hellis = new Object();
synchronized(hellis) {
System.out.println(hellis);
}
}

  • 注意:字节码文件中并没有进行优化,可以看到加锁和释放锁的操作依然存在,同步省略操作是在解释运行时发生的

image.png

关闭锁消除优化-XX:-EliminateLocks`