Monitor 原理
Monitor 被翻译为监视器或管程
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的Mark Word 中就被设置指向 Monitor 对象的指针
Monitor 结构如下
- 刚开始 Monitor 中 Owner 为 null
- 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一
个 Owner - 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入
EntryList BLOCKED - Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的
- 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲
wait-notify 时会分析注意:
- synchronized 必须是进入同一个对象的 monitor 才有上述的效果
- 不加 synchronized 的对象不会关联监视器,不遵从以上规则
synchronized 原理
static final Object lock = new Object();static int counter = 0;public static void main(String[] args) {synchronized (lock) {counter++;}}
对应的字节码为
public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=3, args_size=10: getstatic #2 // <- lock引用 (synchronized开始)3: dup // 为什么要复制并保存一份,是为了后面解锁,你不能只加锁,不解锁4: astore_1 // lock引用 -> slot 15: monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针6: getstatic #3 // <- i9: iconst_1 // 准备常数 110: iadd // +111: putstatic #3 // -> i14: aload_1 // <- lock引用 // 拿到刚刚存储的临时变量 //根据MarkWord对象头找到Monitor,然后调用monitorexit指令15: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList // 这里重置MarkWord,对于分代年龄什么的,都在Monitor中了,可以取出来16: goto 24 // 明明已经结束了 为什么还有下面这些?? 因为上面释放锁是正常情况 这里考虑了异常19: astore_2 // e -> slot 2 // 基本就是保存异常对象;取出lock引用,重置和唤醒;加载异常对象;抛出异常;return20: aload_1 // <- lock引用21: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList22: aload_2 // <- slot 2 (e)23: athrow // throw e24: return // 这样就保证了加了锁,一定可以解开。Exception table:from to target type6 16 19 any19 22 19 anyLineNumberTable:line 8: 0line 9: 6line 10: 14line 11: 24LocalVariableTable:Start Length Slot Name Signature0 25 0 args [Ljava/lang/String;StackMapTable: number_of_entries = 2frame_type = 255 /* full_frame */offset_delta = 19locals = [ class "[Ljava/lang/String;", class java/lang/Object ]stack = [ class java/lang/Throwable ]frame_type = 250 /* chop */offset_delta = 4
根据上面我们知道,对于synchronized加锁,其实是依赖Monitor对象,这个是由操作系统提供的;其实是非常耗费性能的。因此从jdk1.6就改进了这个synchronized获取锁的方式,优化。
注意 方法级别的 synchronized 不会在字节码指令中有所体现
synchronized 原理
1. 轻量级锁
轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
轻量级锁对使用者是透明的,即语法仍然是 synchronized
假设有两个方法同步块,利用同一个对象加锁
static final Object obj = new Object();public static void method1() {synchronized( obj ) {// 同步块 Amethod2();}}public static void method2() {synchronized( obj ) {// 同步块 B}}
- 创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word

- 让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存
入锁记录

- 如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下

- 如果 cas 失败,有两种情况
- 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程(本来就是00)
- 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数(这种失败没有关系,是锁重入)

- 当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重
入计数减一(这个直接清除锁记录即可。)

当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头
当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

- 这时 Thread-1 加轻量级锁失败,进入锁膨胀流程
- 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
- 然后自己进入 Monitor 的 EntryList BLOCKED

当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁
流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程3. 自旋优化(有一点疑问)
重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
自旋重试成功的情况
自旋重试失败的情况
自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
- 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
- Java 7 之后不能控制是否开启自旋功能
4. 偏向锁
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有
例如: ```java static final Object obj = new Object(); public static void m1() { synchronized( obj ) {
} }// 同步块 Am2();
public static void m2() { synchronized( obj ) { // 同步块 B m3(); } }
public static void m3() { synchronized( obj ) { // 同步块 C } }
<br /><a name="Kgo1P"></a>### 偏向状态回忆一下对象头格式<br /><br />一个对象创建时:- 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0- 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -<br />XX:BiasedLockingStartupDelay=0 来禁用延迟- 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、<br />age 都为 0,第一次用到 hashcode 时才会赋值1) 测试延迟特性<br />2) 测试偏向锁```javaclass Dog {}
利用 jol 第三方工具来查看对象头信息(注意这里我扩展了 jol 让它输出更为简洁)
// 添加虚拟机参数 -XX:BiasedLockingStartupDelay=0public static void main(String[] args) throws IOException {Dog d = new Dog();ClassLayout classLayout = ClassLayout.parseInstance(d);new Thread(() -> {log.debug("synchronized 前");System.out.println(classLayout.toPrintableSimple(true));synchronized (d) {log.debug("synchronized 中");System.out.println(classLayout.toPrintableSimple(true));}log.debug("synchronized 后");System.out.println(classLayout.toPrintableSimple(true));}, "t1").start();}
输出
11:08:58.117 c.TestBiased [t1] - synchronized 前00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000010111:08:58.121 c.TestBiased [t1] - synchronized 中00000000 00000000 00000000 00000000 00011111 11101011 11010000 0000010111:08:58.121 c.TestBiased [t1] - synchronized 后00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101
注意 处于偏向锁的对象解锁后,线程 id 仍存储于对象头中
3)测试禁用
在上面测试代码运行时在添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁
输出
11:13:10.018 c.TestBiased [t1] - synchronized 前00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000000111:13:10.021 c.TestBiased [t1] - synchronized 中00000000 00000000 00000000 00000000 00100000 00010100 11110011 1000100011:13:10.021 c.TestBiased [t1] - synchronized 后00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
4) 测试 hashCode
正常状态对象一开始是没有 hashCode 的,第一次调用才生成
撤销 - 调用对象 hashCode
调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被
撤销轻量级锁会在锁记录中记录 hashCode
- 重量级锁会在 Monitor 中记录 hashCode
在调用 hashCode 后使用偏向锁,记得去掉 -XX:-UseBiasedLocking
输出
11:22:10.386 c.TestBiased [main] - 调用 hashCode:177853501511:22:10.391 c.TestBiased [t1] - synchronized 前00000000 00000000 00000000 01101010 00000010 01001010 01100111 0000000111:22:10.393 c.TestBiased [t1] - synchronized 中00000000 00000000 00000000 00000000 00100000 11000011 11110011 0110100011:22:10.393 c.TestBiased [t1] - synchronized 后00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
撤销 - 其它线程使用对象
当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
private static void test2() throws InterruptedException {Dog d = new Dog();Thread t1 = new Thread(() -> {synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}synchronized (TestBiased.class) {TestBiased.class.notify();}// 如果不用 wait/notify 使用 join 必须打开下面的注释// 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的/*try {System.in.read();} catch (IOException e) {e.printStackTrace();}*/}, "t1");t1.start();Thread t2 = new Thread(() -> {synchronized (TestBiased.class) {try {TestBiased.class.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}, "t2");t2.start();}
输出
[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
撤销 - 调用 wait/notify
public static void main(String[] args) throws InterruptedException {Dog d = new Dog();Thread t1 = new Thread(() -> {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));synchronized (d) {log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));try {d.wait();} catch (InterruptedException e) {e.printStackTrace();}log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));}}, "t1");t1.start();new Thread(() -> {try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (d) {log.debug("notify");d.notify();}}, "t2").start();}
输出
[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101[t2] - notify[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010
批量重定向
如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID
当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程
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();}
输出
[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
批量撤销
当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向
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));}
参考资料
https://github.com/farmerjohngit/myblog/issues/12
https://www.cnblogs.com/LemonFive/p/11246086.html
https://www.cnblogs.com/LemonFive/p/11248248.html
偏向锁论文
5. 锁消除
@Fork(1)@BenchmarkMode(Mode.AverageTime)@Warmup(iterations=3)@Measurement(iterations=5)@OutputTimeUnit(TimeUnit.NANOSECONDS)public class MyBenchmark {static int x = 0;@Benchmarkpublic void a() throws Exception {x++;}@Benchmarkpublic void b() throws Exception {Object o = new Object();synchronized (o) {x++;}}}
java -jar benchmarks.jar
Benchmark Mode Samples Score Score error Unitsc.i.MyBenchmark.a avgt 5 1.542 0.056 ns/opc.i.MyBenchmark.b avgt 5 1.518 0.091 ns/op
java -XX:-EliminateLocks -jar benchmarks.jar
Benchmark Mode Samples Score Score error Unitsc.i.MyBenchmark.a avgt 5 1.507 0.108 ns/opc.i.MyBenchmark.b avgt 5 16.976 1.572 ns/op
锁粗化
对相同对象多次加锁,导致线程发生多次重入,可以使用锁粗化方式来优化,这不同于之前讲的细分锁的粒度。
wait notify 原理

- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
- BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
- BLOCKED 线程会在 Owner 线程释放锁时唤醒
- WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争
join 原理
这个讲完,直接讲join 因为其用的就是刚刚的保护性暂停模式 区别是保护性暂停模式:一个线程等待另一个线程的结果 join是一个线程等待另一个线程的结束
是调用者轮询检查线程 alive 状态
t1.join();
等价于下面的代码
synchronized (t1) {// 调用者线程进入 t1 的 waitSet 等待, 直到 t1 运行结束while (t1.isAlive()) {t1.wait(0);}}
注意 join 体现的是【保护性暂停】模式,请参考之 看下源码,和前面的保护性暂停模式增强版一模一样。
park unpark 原理
每个线程都有自己的一个 Parker 对象,由三部分组成 _counter , _cond 和 _mutex 打个比喻
- 线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中
的备用干粮(0 为耗尽,1 为充足) - 调用 park 就是要看需不需要停下来歇息
- 如果备用干粮耗尽,那么钻进帐篷歇息
- 如果备用干粮充足,那么不需停留,继续前进
- 调用 unpark,就好比令干粮充足
- 如果这时线程还在帐篷,就唤醒让他继续前进
- 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
- 因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮

1. 当前线程调用 Unsafe.park() 方法
2. 检查 _counter ,本情况为 0,这时,获得 _mutex 互斥锁(没有干粮:thread0就进入mutex,mutex也相当于是一个对象,其里面有一个等待队列就是cond)
3. 线程进入 _cond 条件变量阻塞
4. 设置 _counter = 0
1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
2. 唤醒 _cond 条件变量中的 Thread_0
3. Thread_0 恢复运行
4. 设置 _counter 为 0
1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
2. 当前线程调用 Unsafe.park() 方法
3. 检查 _counter ,本情况为 1,这时线程无需阻塞,继续运行
4. 设置 _counter 为 0
CPU缓存结构原理
1. CPU缓存结构

查看 cpu 缓存
⚡ root@yihang01 ~ lscpuArchitecture: x86_64CPU op-mode(s): 32-bit, 64-bitByte Order: Little EndianCPU(s): 1On-line CPU(s) list: 0Thread(s) per core: 1Core(s) per socket: 1Socket(s): 1NUMA node(s): 1Vendor ID: GenuineIntelCPU family: 6Model: 142Model name: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHzStepping: 11CPU MHz: 1992.002BogoMIPS: 3984.00Hypervisor vendor: VMwareVirtualization type: fullL1d cache: 32KL1i cache: 32KL2 cache: 256KL3 cache: 8192KNUMA node0 CPU(s): 0
速度比较
查看cpu缓存行
⚡ root@yihang01 ~ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size64
cpu 拿到的内存地址格式是这样的
[高位组标记][低位索引][偏移量]
2. CPU缓存读
读取数据流程如下
- 根据低位,计算在缓存中的索引
判断是否有效
- 0 去内存读取新数据更新缓存行
- 1 再对比高位组标记是否一致
- 一致,根据偏移量返回缓存数据
- 不一致,去内存读取新数据更新缓存行
3. CPU缓存一致性
MESI 协议
1. E、S、M 状态的缓存行都可以满足 CPU 的读请求
2. E 状态的缓存行,有写请求,会将状态改为 M,这时并不触发向主存的写
3. E 状态的缓存行,必须监听该缓存行的读操作,如果有,要变为 S 状态
4. M 状态的缓存行,必须监听该缓存行的读操作,如果有,先将其它缓存(S 状态)中该缓存行变成 I 状态(即
6. 的流程),写入主存,自己变为 S 状态
5. S 状态的缓存行,有写请求,走 4. 的流程
6. S 状态的缓存行,必须监听该缓存行的失效操作,如果有,自己变为 I 状态
7. I 状态的缓存行,有读请求,必须从主存读取
4. 内存屏障
Memory Barrier(Memory Fence)
可见性
- 写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
- 而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
- 有序性
- 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
- 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
指令级并行原理
1. 名词
Clock Cycle Time
主频的概念大家接触的比较多,而 CPU 的 Clock Cycle Time(时钟周期时间),等于主频的倒数,意思是 CPU 能够识别的最小时间单位,比如说 4G 主频的 CPU 的 Clock Cycle Time 就是 0.25 ns,作为对比,我们墙上挂钟的
Cycle Time 是 1s
例如,运行一条加法指令一般需要一个时钟周期时间
CPI
有的指令需要更多的时钟周期时间,所以引出了 CPI (Cycles Per Instruction)指令平均时钟周期数
IPC
IPC(Instruction Per Clock Cycle) 即 CPI 的倒数,表示每个时钟周期能够运行的指令数
CPU执行时间
程序的 CPU 执行时间,即我们前面提到的 user + system 时间,可以用下面的公式来表示
2. 鱼罐头的故事
加工一条鱼需要 50 分钟,只能一条鱼、一条鱼顺序加工…
可以将每个鱼罐头的加工流程细分为 5 个步骤:
- 去鳞清洗 10分钟
- 蒸煮沥水 10分钟
- 加注汤料 10分钟
- 杀菌出锅 10分钟
- 真空封罐 10分钟
3. 指令重排序优化
事实上,现代处理器会设计为一个时钟周期完成一条执行时间最长的 CPU 指令。为什么这么做呢?可以想到指令还可以再划分成一个个更小的阶段,例如,每条指令都可以分为: 取指令 - 指令译码 - 执行指令 - 内存访问 - 数据写回 这 5 个阶段 (其实就像是一条指令拆分成了五条,当然不同CPU拆分条数不同,但现在应该大部分是五条)

在不改变程序结果的前提下,这些指令的各个阶段可以通过重排序和组合来实现指令级并行,这一技术在 80’s 中叶到 90’s 中叶占据了计算架构的重要地位。
提示: 分阶段,分工是提升效率的关键!
指令重排的前提是,重排指令不能影响结果,例如
// 可以重排的例子int a = 10; // 指令1int b = 20; // 指令2System.out.println( a + b );// 不能重排的例子int a = 10; // 指令1int b = a - 5; // 指令2
参考: Scoreboarding and the Tomasulo algorithm (which is similar to scoreboarding but makes use of
register renaming) are two of the most common techniques for implementing out-of-order execution
and instruction-level parallelism.
4. 支持流水线的优化器
现代 CPU 支持多级指令流水线,例如支持同时执行 取指令 - 指令译码 - 执行指令 - 内存访问 - 数据写回 的处理器,就可以称之为五级指令流水线。这时 CPU 可以在一个时钟周期内,同时运行五条指令的不同阶段(相当于一条执行时间最长的复杂指令),IPC = 1,本质上,流水线技术并不能缩短单条指令的执行时间,但它变相地提高了指令地吞吐率。
提示: 奔腾四(Pentium 4)支持高达 35 级流水线,但由于功耗太高被废弃
5. SuperScalar 处理器
大多数处理器包含多个执行单元,并不是所有计算功能都集中在一起,可以再细分为整数运算单元、浮点数运算单元等,这样可以把多条指令也可以做到并行获取、译码等,CPU 可以在一个时钟周期内,执行多于一条指令,IPC> 1
volatile原理
volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
- 对 volatile 变量的写指令后会加入写屏障
-
1. 如何保证可见性
写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
public void actor2(I_Result r) {num = 2;ready = true; // ready 是 volatile 赋值带写屏障// 写屏障}
而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
public void actor1(I_Result r) {// 读屏障// ready 是 volatile 读取值带读屏障if(ready) {r.r1 = num + num;} else {r.r1 = 1;}}
2. 如何保证有序性
写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
public void actor2(I_Result r) {num = 2;ready = true; // ready 是 volatile 赋值带写屏障// 写屏障}
读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
public void actor1(I_Result r) {// 读屏障// ready 是 volatile 读取值带读屏障if(ready) {r.r1 = num + num;} else {r.r1 = 1;}}

还是那句话,不能解决指令交错:写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
- 而有序性的保证也只是保证了本线程内相关代码不被重排序
3. double-checked locking 问题
以著名的 double-checked locking 单例模式为例
public final class Singleton {private Singleton() { }private static Singleton INSTANCE = null;public static Singleton getInstance() {if(INSTANCE == null) { // t2// 首次访问会同步,而之后的使用没有 synchronizedsynchronized(Singleton.class) {if (INSTANCE == null) { // t1INSTANCE = new Singleton();}}}return INSTANCE;}}
以上的实现特点是:
- 懒惰实例化
- 首次使用 getInstance() 才使用 synchronized 加锁,后续使用时无需加锁
- 有隐含的,但很关键的一点:第一个 if 使用了 INSTANCE 变量,是在同步块之外
但在多线程环境下,上面的代码是有问题的,getInstance 方法对应的字节码为:
0: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;3: ifnonnull 376: ldc #3 // class cn/itcast/n5/Singleton8: dup9: astore_010: monitorenter11: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;14: ifnonnull 2717: new #3 // class cn/itcast/n5/Singleton20: dup21: invokespecial #4 // Method "<init>":()V24: putstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;27: aload_028: monitorexit29: goto 3732: astore_133: aload_034: monitorexit35: aload_136: athrow37: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;40: areturn
其中
- 17 表示创建对象,将对象引用入栈 // new Singleton
- 20 表示复制一份对象引用 // 引用地址
- 21 表示利用一个对象引用,调用构造方法
- 24 表示利用一个对象引用,赋值给 static INSTANCE
也许 jvm 会优化为:先执行 24,再执行 21。如果两个线程 t1,t2 按如下时间序列执行:
关键在于 0: getstatic 这行代码在 monitor 控制之外,它就像之前举例中不守规则的人,可以越过 monitor 读取INSTANCE 变量的值
这时 t1 还未完全将构造方法执行完毕,如果在构造方法中要执行很多初始化操作,那么 t2 拿到的是将是一个未初始化完毕的单例
对 INSTANCE 使用 volatile 修饰即可,可以禁用指令重排,但要注意在 JDK 5 以上的版本的 volatile 才会真正有效
4. double-checked locking 解决
public final class Singleton {private Singleton() { }private static volatile Singleton INSTANCE = null;public static Singleton getInstance() {// 实例没创建,才会进入内部的 synchronized代码块if (INSTANCE == null) {synchronized (Singleton.class) { // t2// 也许有其它线程已经创建实例,所以再判断一次if (INSTANCE == null) { // t1INSTANCE = new Singleton();}}}return INSTANCE;}}
字节码上看不出来 volatile 指令的效果
// -------------------------------------> 加入对 INSTANCE 变量的读屏障0: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;3: ifnonnull 376: ldc #3 // class cn/itcast/n5/Singleton8: dup9: astore_010: monitorenter -----------------------> 保证原子性、可见性11: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;14: ifnonnull 2717: new #3 // class cn/itcast/n5/Singleton20: dup21: invokespecial #4 // Method "<init>":()V24: putstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;// -------------------------------------> 加入对 INSTANCE 变量的写屏障27: aload_028: monitorexit ------------------------> 保证原子性、可见性29: goto 3732: astore_133: aload_034: monitorexit35: aload_136: athrow37: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;40: areturn
如上面的注释内容所示,读写 volatile 变量时会加入内存屏障(Memory Barrier(Memory Fence)),保证下面
两点:
- 可见性
- 写屏障(sfence)保证在该屏障之前的 t1 对共享变量的改动,都同步到主存当中
- 而读屏障(lfence)保证在该屏障之后 t2 对共享变量的读取,加载的是主存中最新数据
- 有序性
- 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
- 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
- 更底层是读写变量时使用 lock 指令来多核 CPU 之间的可见性与有序性

