1. 共享模型之管程

1.1 共享带来的问题

1.1.1 Java 的体现

  1. @Slf4j
  2. public class ShareQuestion {
  3. static int counter = 0;
  4. public static void main(String[] args) throws InterruptedException {
  5. Thread t1 = new Thread(() -> {
  6. for (int i = 0; i < 5000; i++) {
  7. counter++;
  8. }
  9. }, "t1");
  10. Thread t2 = new Thread(() -> {
  11. for (int i = 0; i < 5000; i++) {
  12. counter--;
  13. }
  14. }, "t2");
  15. t1.start();
  16. t2.start();
  17. t1.join();
  18. t2.join();
  19. log.info("counter 结果是 {}", counter);
  20. }
  21. }

问题分析
以上的结果可能是正数、负数、零。为什么呢?因为 Java 中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码来进行分析

  1. getstatic i // 获取静态变量 i 的值
  2. iconst_1 // 准备常量 1
  3. iadd // 自增
  4. putstatic i // 将修改后的值存入静态变量 i
  1. getstatic i // 获取静态变量 i 的值
  2. iconst_1 // 准备常量 1
  3. isub // 自减
  4. putstatic i // 将修改后的值存入静态变量 i

如果是单线程执行这 8 行代码,是不会发生线程安全问题
image.png
如果是多线程执行这 8 行代码,会发生线程安全问题(可能出现负数或者正数)
image.png

1.1.2 临界区 Critical Section

一个程序运行多个线程本身是没有问题的

问题出在多个线程访问共享资源

  • 多个线程读共享资源其实也没有问题
  • 在多个线程对共享资源读写操作时发生指令交错,就会出现问题

一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

  1. static int counter = 0;
  2. static void increment() {
  3. // 临界区
  4. counter++;
  5. }
  6. static void decrement() {
  7. // 临界区
  8. counter--;
  9. }

1.1.3 竞态条件 Race Condition

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

1.2 Synchronized

虽然 java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的

  • 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
  • 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点

为了避免临界区的竞态条件发生,有多种手段可以达到目的

  • 阻塞式的解决方案:synchronized,Lock
  • 非阻塞式的解决方案:原子变量

它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换

  1. @Slf4j
  2. public class ShareQuestionSync {
  3. static int counter = 0;
  4. static final Object room = new Object();
  5. public static void main(String[] args) throws InterruptedException {
  6. Thread t1 = new Thread(() -> {
  7. for (int i = 0; i < 5000; i++) {
  8. synchronized (room) {
  9. counter++;
  10. }
  11. }
  12. }, "t1");
  13. Thread t2 = new Thread(() -> {
  14. for (int i = 0; i < 5000; i++) {
  15. synchronized (room) {
  16. counter--;
  17. }
  18. }
  19. }, "t2");
  20. t1.start();
  21. t2.start();
  22. t1.join();
  23. t2.join();
  24. log.info("counter 结果是 {}", counter);
  25. }
  26. }

可以这样理解

  • synchronized(对象)中的对象,可以想象为一个房间(room),有唯一入口(门)房间只能一次进入一人进行计算,线程 t1,t2 想象成两个人
  • 当线程 t1 执行到 synchronized(room) 时就好比 t1 进入了这个房间,并锁住了门拿走了钥匙,在门内执行 count++ 代码
  • 这时候如果 t2 也运行到了 synchronized(room) 时,它发现门被锁住了,只能在门外等待,发生了上下文切换,阻塞住了
  • 这中间即使 t1 的 cpu 时间片不幸用完,被踢出了门外(不要错误理解为锁住了对象就能一直执行下去),这时门还是锁住的,t1 仍拿着钥匙,t2 线程还在阻塞状态进不来,只有下次轮到 t1 自己再次获得时间片时才能开门进入
  • 当 t1 执行完 synchronized{} 块内的代码,这时候才会从 obj 房间出来并解开门上的锁,唤醒 t2 线程把钥匙给他。t2 线程这时才可以进入 obj 房间,锁住了门拿上钥匙,执行它的 count-- 代码

synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断

1.3 synchronized 加在方法上

  1. class Test{
  2. public synchronized void test() {
  3. }
  4. }
  5. // 等价于实例锁
  6. class Test{
  7. public void test() {
  8. synchronized(this) {
  9. }
  10. }
  11. }
  1. class Test{
  2. public synchronized static void test() {
  3. }
  4. }
  5. // 等价于类锁
  6. class Test{
  7. public static void test() {
  8. synchronized(Test.class) {
  9. }
  10. }
  11. }

1.4 线程八锁

  1. @Slf4j
  2. class Number {
  3. public synchronized void a() {
  4. log.debug("1");
  5. }
  6. public synchronized void b() {
  7. log.debug("2");
  8. }
  9. }
  10. public static void main(String[] args) {
  11. Number n1 = new Number();
  12. new Thread(()->{ n1.a(); }).start();
  13. new Thread(()->{ n1.b(); }).start();
  14. }
  1. @Slf4j
  2. class Number{
  3. public synchronized void a() {
  4. try {
  5. TimeUnit.SECONDS.sleep(1);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. log.debug("1");
  10. }
  11. public synchronized void b() {
  12. log.debug("2");
  13. }
  14. }
  15. public static void main(String[] args) {
  16. Number n1 = new Number();
  17. new Thread(()->{ n1.a(); }).start();
  18. new Thread(()->{ n1.b(); }).start();
  19. }
  1. @Slf4j
  2. class Number{
  3. public synchronized void a() {
  4. try {
  5. TimeUnit.SECONDS.sleep(1);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. log.debug("1");
  10. }
  11. public synchronized void b() {
  12. log.debug("2");
  13. }
  14. public void c() {
  15. log.debug("3");
  16. }
  17. }
  18. public static void main(String[] args) {
  19. Number n1 = new Number();
  20. new Thread(()->{ n1.a(); }).start();
  21. new Thread(()->{ n1.b(); }).start();
  22. new Thread(()->{ n1.c(); }).start();
  23. }
  1. @Slf4j
  2. class Number{
  3. public synchronized void a() {
  4. try {
  5. TimeUnit.SECONDS.sleep(1);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. log.debug("1");
  10. }
  11. public synchronized void b() {
  12. log.debug("2");
  13. }
  14. }
  15. public static void main(String[] args) {
  16. Number n1 = new Number();
  17. Number n2 = new Number();
  18. new Thread(()->{ n1.a(); }).start();
  19. new Thread(()->{ n2.b(); }).start();
  20. }
  1. @Slf4j
  2. class Number {
  3. public static synchronized void a() {
  4. try {
  5. TimeUnit.SECONDS.sleep(1);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. log.debug("1");
  10. }
  11. public synchronized void b() {
  12. log.debug("2");
  13. }
  14. }
  15. public static void main(String[] args) {
  16. Number n1 = new Number();
  17. new Thread(()->{ n1.a(); }).start();
  18. new Thread(()->{ n1.b(); }).start();
  19. }
  1. @Slf4j
  2. class Number {
  3. public static synchronized void a() {
  4. try {
  5. TimeUnit.SECONDS.sleep(1);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. log.debug("1");
  10. }
  11. public static synchronized void b() {
  12. log.debug("2");
  13. }
  14. }
  15. public static void main(String[] args) {
  16. Number n1 = new Number();
  17. new Thread(()->{ n1.a(); }).start();
  18. new Thread(()->{ n1.b(); }).start();
  19. }
  1. @Slf4j
  2. class Number {
  3. public static synchronized void a() {
  4. try {
  5. TimeUnit.SECONDS.sleep(1);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. log.debug("1");
  10. }
  11. public synchronized void b() {
  12. log.debug("2");
  13. }
  14. }
  15. public static void main(String[] args) {
  16. Number n1 = new Number();
  17. Number n2 = new Number();
  18. new Thread(()->{ n1.a(); }).start();
  19. new Thread(()->{ n2.b(); }).start();
  20. }
  1. @Slf4j
  2. class Number {
  3. public static synchronized void a() {
  4. try {
  5. TimeUnit.SECONDS.sleep(1);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. log.debug("1");
  10. }
  11. public static synchronized void b() {
  12. log.debug("2");
  13. }
  14. }
  15. public static void main(String[] args) {
  16. Number n1 = new Number();
  17. Number n2 = new Number();
  18. new Thread(()->{ n1.a(); }).start();
  19. new Thread(()->{ n2.b(); }).start();
  20. }

1.5 变量的线程安全分析

1.5.1 成员变量和静态变量

如果它们没有共享,则线程安全

如果它们被共享了,根据它们的状态是否能够改变,又分两种情况

  • 如果只有读操作,则线程安全
  • 如果有读写操作,则这段代码是临界区,需要考虑线程安全

    1.5.2 局部变量

    局部变量是线程安全的

但局部变量引用的对象则未必

  • 如果该对象没有逃离方法的作用访问,它是线程安全的
  • 如果该对象逃离方法的作用范围,需要考虑线程安全

    1.6 Monitor 概念

    1.6.1 Java 对象头

    32 位虚拟机 Mark Word
    普通对象
Object Header(64 bits)
Mark Word(32 bits) Klass Word(32 bits)

数组对象

Object Header(96 bits)
Mark Word(32 bits) Klass Word(32 bits) array length(32 bits)

Mark Word 结构

Mark Word(64 bits) State
hashcode:25 age:4 biased_lock:0 01 Normal
thread:23 epoch:2 age:4 biased_lock:1 01 Biased
ptr_to_lock_record:30 00 Lightweight Locked
ptr_to_heavyweight_monitor:30 10 Heavyweight Locked
11 Marked for GC

64 位虚拟机 Mark Word 结构

Mark Word(64 bits) State
unused:25 hashcode:31 unused:1 age:4 biased_lock:0 01 Normal
thread:54 epoch:2 unused:1 age:4 biased_lock:1 01 Biased
ptr_to_lock_record:62 00 Lightweight Locked
ptr_to_heavyweight_monitor:62 10 Heavyweight Locked
11 Marked for GC

1.6.2 Monitor(管程)原理

Monitor 被翻译为监视器或管程
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针
image.png
刚开始 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(进入等待队列的线程 CPU 不会调度)

Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的

图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程

1.6.3 synchronized 原理

方法级别的 synchronized 不会在字节码指令中有所体现

  1. public class SyncBytecode {
  2. static final Object lock = new Object();
  3. static int counter = 0;
  4. public static void main(String[] args) {
  5. synchronized (lock) {
  6. counter++;
  7. }
  8. }
  9. }
  1. {
  2. static final java.lang.Object lock;
  3. descriptor: Ljava/lang/Object;
  4. flags: ACC_STATIC, ACC_FINAL
  5. static int counter;
  6. descriptor: I
  7. flags: ACC_STATIC
  8. public org.masteryourself.tutorial.sync.SyncBytecode();
  9. descriptor: ()V
  10. flags: ACC_PUBLIC
  11. Code:
  12. stack=1, locals=1, args_size=1
  13. 0: aload_0
  14. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  15. 4: return
  16. LineNumberTable:
  17. line 12: 0
  18. LocalVariableTable:
  19. Start Length Slot Name Signature
  20. 0 5 0 this Lorg/masteryourself/tutorial/sync/SyncBytecode;
  21. public static void main(java.lang.String[]);
  22. descriptor: ([Ljava/lang/String;)V
  23. flags: ACC_PUBLIC, ACC_STATIC
  24. Code:
  25. stack=2, locals=3, args_size=1
  26. 0: getstatic #2 // Field lock:Ljava/lang/Object;
  27. 3: dup
  28. 4: astore_1
  29. 5: monitorenter
  30. 6: getstatic #3 // Field counter:I
  31. 9: iconst_1
  32. 10: iadd
  33. 11: putstatic #3 // Field counter:I
  34. 14: aload_1
  35. 15: monitorexit
  36. 16: goto 24
  37. 19: astore_2
  38. 20: aload_1
  39. 21: monitorexit
  40. 22: aload_2
  41. 23: athrow
  42. 24: return
  43. Exception table:
  44. from to target type
  45. 6 16 19 any
  46. 19 22 19 any
  47. LineNumberTable:
  48. line 18: 0
  49. line 19: 6
  50. line 20: 14
  51. line 21: 24
  52. LocalVariableTable:
  53. Start Length Slot Name Signature
  54. 0 25 0 args [Ljava/lang/String;
  55. StackMapTable: number_of_entries = 2
  56. frame_type = 255 /* full_frame */
  57. offset_delta = 19
  58. locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
  59. stack = [ class java/lang/Throwable ]
  60. frame_type = 250 /* chop */
  61. offset_delta = 4
  62. static {};
  63. descriptor: ()V
  64. flags: ACC_STATIC
  65. Code:
  66. stack=2, locals=0, args_size=0
  67. 0: new #4 // class java/lang/Object
  68. 3: dup
  69. 4: invokespecial #1 // Method java/lang/Object."<init>":()V
  70. 7: putstatic #2 // Field lock:Ljava/lang/Object;
  71. 10: iconst_0
  72. 11: putstatic #3 // Field counter:I
  73. 14: return
  74. LineNumberTable:
  75. line 14: 0
  76. line 15: 10
  77. }

1.6.4 synchronized 锁优化

1.6.4.1 轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
轻量级锁对使用者是透明的,即语法仍然是 synchronized
假设有两个方法同步块,利用同一个对象加锁

  1. public class SyncLight {
  2. static final Object obj = new Object();
  3. public static void method1() {
  4. synchronized( obj ) {
  5. // 同步块 A
  6. method2();
  7. }
  8. }
  9. public static void method2() {
  10. synchronized( obj ) {
  11. // 同步块 B
  12. }
  13. }
  14. }
  1. 创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word

image.png

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

image.png

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

image.png

  1. 如果 cas 失败,存在两种情况
    • 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
    • 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数

image.png

  1. 当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一
  2. 当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头
    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

      1.6.4.2 重量级锁

      如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。 ```java static Object obj = new Object();

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

  1. 1. **当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁**
  2. <a name="X8Qgn"></a>
  3. #### ![image.png](https://cdn.nlark.com/yuque/0/2022/png/432786/1650947505050-5267d9b4-b7ff-4c2a-8c46-2b6d5b293496.png#clientId=u2c9ea868-6ec8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=399&id=u9f6b0d6c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=798&originWidth=2070&originalType=binary&ratio=1&rotation=0&showTitle=false&size=493797&status=done&style=none&taskId=ue7bcf492-91e1-48d8-9290-32e3e5c9eac&title=&width=1035)
  4. 2. **这时 Thread-1 加轻量级锁失败,进入锁膨胀流程**
  5. - 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
  6. - 然后自己进入 Monitor EntryList BLOCKED
  7. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/432786/1650947614758-65646117-0518-44fe-a852-5b929849eaa9.png#clientId=u2c9ea868-6ec8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=429&id=u77041988&margin=%5Bobject%20Object%5D&name=image.png&originHeight=858&originWidth=2154&originalType=binary&ratio=1&rotation=0&showTitle=false&size=113023&status=done&style=none&taskId=u432b35be-9469-4a5d-9fbb-5fa54a5a128&title=&width=1077)
  8. 3. **当 Thread-0 退出同步块解锁时,使用 cas Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner null,唤醒 EntryList BLOCKED 线程**
  9. <a name="V6o2Q"></a>
  10. #### 1.6.4.3 自旋优化
  11. 重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
  12. 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  13. Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
  14. Java 7 之后不能控制是否开启自旋功能
  15. <a name="OJbTy"></a>
  16. #### 1.6.4.4 偏向锁
  17. 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
  18. Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有
  19. ```java
  20. static final Object obj = new Object();
  21. public static void m1() {
  22. synchronized (obj) {
  23. // 同步块 A
  24. m2();
  25. }
  26. }
  27. public static void m2() {
  28. synchronized (obj) {
  29. // 同步块 B
  30. m3();
  31. }
  32. }
  33. public static void m3() {
  34. synchronized (obj) {
  35. // 同步块 C
  36. }
  37. }
轻量级锁
m1 内调用 synchronized (_obj_) 用锁记录替换 markword
m2 内调用 synchronized (_obj_) 用锁记录替换 markword
m3 内调用 synchronized (_obj_) 用锁记录替换 markword
偏向锁
m1 内调用 synchronized (_obj_) 用 ThreadId 替换 markword
m2 内调用 synchronized (_obj_) 检查 ThreadId 是否是自己
m3 内调用 synchronized (_obj_) 检查 ThreadId 是否是自己

偏向状态
一个对象创建时

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的 thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值 ```java // 需要添加 VM 参数禁用延迟 -XX:BiasedLockingStartupDelay=0 public static void main(String[] args) throws Exception { Dog dog = new Dog(); // 0x0000000000000005 (biasable; age: 0) => 05=0000 0101(此时具有偏向锁) System.out.println(ClassLayout.parseInstance(dog).toPrintable()); }

static class Dog {

}

  1. ```java
  2. // 需要添加 VM 参数禁用延迟 -XX:BiasedLockingStartupDelay=0
  3. public static void main(String[] args) {
  4. Dog d = new Dog();
  5. ClassLayout classLayout = ClassLayout.parseInstance(d);
  6. new Thread(() -> {
  7. log.info("synchronized 前");
  8. // 0x0000000000000005 (biasable; age: 0) => 05=0000 0101(此时具有偏向锁)
  9. System.out.println(classLayout.toPrintable());
  10. synchronized (d) {
  11. log.info("synchronized 中");
  12. // 0x00007fc579af5005 (biased: 0x0000001ff15e6bd4; epoch: 0; age: 0) => 线程 id + 偏向锁
  13. System.out.println(classLayout.toPrintable());
  14. }
  15. log.info("synchronized 后");
  16. // 0x00007fc579af5005 (biased: 0x0000001ff15e6bd4; epoch: 0; age: 0) => 线程 id + 偏向锁
  17. System.out.println(classLayout.toPrintable());
  18. }, "t2").start();
  19. }
  20. static class Dog {
  21. }
  1. // 需要添加 VM 参数禁用偏向锁 -XX:-UseBiasedLocking
  2. public static void main(String[] args) throws Exception {
  3. Dog d = new Dog();
  4. ClassLayout classLayout = ClassLayout.parseInstance(d);
  5. new Thread(() -> {
  6. log.info("synchronized 前");
  7. // 0x0000000000000001 (non-biasable; age: 0) => 01=0000 0001(正常状态)
  8. System.out.println(classLayout.toPrintable());
  9. synchronized (d) {
  10. log.info("synchronized 中");
  11. // 0x000000030eb749e0 (thin lock: 0x000000030eb749e0) => 轻量级锁记录
  12. System.out.println(classLayout.toPrintable());
  13. }
  14. log.info("synchronized 后");
  15. // 0x0000000000000001 (non-biasable; age: 0) => 01=0000 0001(正常状态)
  16. System.out.println(classLayout.toPrintable());
  17. }, "t3").start();
  18. }
  19. static class Dog {
  20. }
  1. public static void main(String[] args) throws Exception {
  2. Dog d = new Dog();
  3. ClassLayout classLayout = ClassLayout.parseInstance(d);
  4. // 0x0000000000000001 (non-biasable; age: 0), 此时 hashcode 没有值
  5. System.out.println(classLayout.toPrintable());
  6. log.info("dog hashcode {}", d.hashCode());
  7. // 0x0000001888ff2c01 (hash: 0x1888ff2c; age: 0), 此时 hashcode 有值
  8. System.out.println(classLayout.toPrintable());
  9. }
  10. static class Dog {
  11. }

撤销-调用对象 hashcode
调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销

  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode ```java // 需要添加 VM 参数禁用延迟 -XX:BiasedLockingStartupDelay=0 public static void main(String[] args) throws Exception { Dog dog = new Dog(); log.info(“dog hashcode {}”, dog.hashCode()); // 0x00000031221be201 (hash: 0x31221be2; age: 0) => 此时没使用偏向锁, 否则 hashcode 无法存储 System.out.println(ClassLayout.parseInstance(dog).toPrintable()); }

static class Dog {

}

  1. **撤销-其它线程使用对象发生锁竞争**<br />当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
  2. ```java
  3. // 需要添加 VM 参数禁用延迟 -XX:BiasedLockingStartupDelay=0
  4. public static void main(String[] args) throws Exception {
  5. Dog d = new Dog();
  6. Thread t1 = new Thread(() -> {
  7. synchronized (d) {
  8. // 0x00007fa841889005 (biased: 0x0000001fea106224; epoch: 0; age: 0) => 偏向锁
  9. log.info(ClassLayout.parseInstance(d).toPrintable());
  10. }
  11. synchronized (RevokeByThread.class) {
  12. RevokeByThread.class.notify();
  13. }
  14. }, "t1");
  15. t1.start();
  16. Thread t2 = new Thread(() -> {
  17. synchronized (RevokeByThread.class) {
  18. try {
  19. RevokeByThread.class.wait();
  20. } catch (InterruptedException e) {
  21. log.error(e.getMessage(), e);
  22. }
  23. }
  24. // 0x00007fa841889005 (biased: 0x0000001fea106224; epoch: 0; age: 0) => 偏向锁
  25. log.info(ClassLayout.parseInstance(d).toPrintable());
  26. synchronized (d) {
  27. // 0x0000000306ab99d8 (thin lock: 0x0000000306ab99d8) => 升级为轻量级锁
  28. log.info(ClassLayout.parseInstance(d).toPrintable());
  29. }
  30. // 0x0000000000000001 (non-biasable; age: 0) => 正常状态
  31. log.info(ClassLayout.parseInstance(d).toPrintable());
  32. }, "t2");
  33. t2.start();
  34. }
  35. static class Dog {
  36. }

撤销-调用 wait/notify

  1. // 需要添加 VM 参数禁用延迟 -XX:BiasedLockingStartupDelay=0
  2. public static void main(String[] args) throws Exception {
  3. Dog d = new Dog();
  4. Thread t1 = new Thread(() -> {
  5. // 0x0000000000000005 (biasable; age: 0) => 偏向锁
  6. log.info(ClassLayout.parseInstance(d).toPrintable());
  7. synchronized (d) {
  8. // 0x00007fcb45167005 (biased: 0x0000001ff2d1459c; epoch: 0; age: 0) => 偏向锁 + 线程 ID
  9. log.info(ClassLayout.parseInstance(d).toPrintable());
  10. try {
  11. d.wait();
  12. } catch (InterruptedException e) {
  13. log.error(e.getMessage(), e);
  14. }
  15. // 0x00007fcb460198ca (fat lock: 0x00007fcb460198ca) => 重量级锁
  16. log.info(ClassLayout.parseInstance(d).toPrintable());
  17. }
  18. }, "t1");
  19. t1.start();
  20. new Thread(() -> {
  21. try {
  22. Thread.sleep(6000);
  23. } catch (InterruptedException e) {
  24. log.error(e.getMessage(), e);
  25. }
  26. synchronized (d) {
  27. log.debug("notify");
  28. d.notify();
  29. }
  30. }, "t2").start();
  31. }
  32. static class Dog {
  33. }

批量重偏向
如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID

当撤销偏向锁阈值超过 20 次后,JVM 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程

批量撤销
当撤销偏向锁阈值超过 40 次后,JVM 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的

1.6.4.5 锁消除

消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java 虚拟机在 JIT 编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间

2. 共享模型之内存

3. 共享模型之无锁

4. 共享模型之不可变

5. 共享模型之工具类