原理剖析之synchronized

1 Java对象头

2 重量级锁Monitor

  • Monitor(重量级锁)被翻译为监视器或管程
  • 每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的Mark Word 中就被设置指向 Monitor 对象的指针

2.1 图解过程

Java对象关联Monitor 过程

  1. 当有多个线程竞争对象锁,Java对象的对象头中的MaekWord会存储指向Monitor对象的指针
    在这里插入图片描述

  2. 当thread2执行到带有obj对象锁的临界区代码块时,thread2就成为了monitor的所有者,通过Owner关联。

    • 注意:Owner只能有一个主人

    在这里插入图片描述

  3. 此时如果thread1和thread3如果执行到临界区代码,它就会检测obj有没有关联monitor锁,如果关联了monitor,就会看它的所有者是谁。此时属于thread2,那么thread1和thread3就会通过EntrySet关联,进入阻塞队列
    在这里插入图片描述

  4. 当thread2执行完了临界区代码,Owner就会断开与thread2的连接,由thread1和thread3竞争monitor的所有权
    在这里插入图片描述

2.2 文字描述

  • 刚开始 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 中的线程是之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲wait-notify 时会分析。

注意:

  • synchronized 必须是进入同一个对象的 monitor 才有上述的效果
  • 不加 synchronized 的对象不会关联监视器,不遵从以上规则

2.3 代码示例

static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
    
    
    synchronized (lock) {
    
    
        counter++;
    }
}
  • 使用javap进行反编译
public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
        stack=2, locals=3, args_size=1
            0: getstatic    #2       // <- lock引用 (synchronized开始)
            3: dup
            4: astore_1               // lock引用 -> slot 1
            5: monitorenter       // 将 lock对象 MarkWord 置为 Monitor 指针
            6: getstatic    #3      // <- i
            9: iconst_1        	     // 准备常数 1
            10: iadd                   // +1
            11: putstatic   #3     // -> i
            14: aload_1              // <- lock引用
            15: monitorexit        // 将 lock对象 MarkWord 重置, 唤醒 EntryList
            16: goto        24       // 执行24行结束
            // 出现异常执行 19 - 23
      
            19: astore_2             // e -> slot 2
            20: aload_1              // <- lock引用
            21: monitorexit       // 将 lock对象 MarkWord 重置, 唤醒 EntryList
            22: aload_2             // <- slot 2 (e)
            23: athrow              // throw e
            24: return
        Exception table:
            from to target type
              6    16    19   any
              19   22   19   any
        LineNumberTable:
            line 8: 0
            line 9: 6
            line 10: 14
            line 11: 24
        LocalVariableTable:
            Start Length Slot Name Signature
              0    25     0   args [Ljava/lang/String;
        StackMapTable: number_of_entries = 2
            frame_type = 255 /* full_frame */
                offset_delta = 19
                locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
                stack = [ class java/lang/Throwable ]
            frame_type = 250 /* chop */
                offset_delta = 4

2.4 自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

自旋重试成功的情况

线程 1 (core 1 上) 对象 Mark 线程 2 (core 2 上)
10(重量锁)
访问同步块,获取 monitor 10(重量锁)重量锁指
成功(加锁) 10(重量锁)重量锁指
执行同步块 10(重量锁)重量锁指
执行同步块 10(重量锁)重量锁指 访问同步块,获取 monitor
执行同步块 10(重量锁)重量锁指 自旋重试
执行完毕 10(重量锁)重量锁指 自旋重试
成功(解锁) 01(无锁) 自旋重试
10(重量锁)重量锁指针 成功(加锁)
10(重量锁)重量锁指针 执行同步块
。。。 。。。 。。。

自旋重试失败的情况

线程 1 (core 1 上) 对象 Mark 线程 2 (core 2 上)
10(重量锁)
访问同步块,获取 monitor 10(重量锁)重量锁指
成功(加锁) 10(重量锁)重量锁指
执行同步块 10(重量锁)重量锁指
执行同步块 10(重量锁)重量锁指 访问同步块,获取 monitor
执行同步块 10(重量锁)重量锁指 自旋重试
执行同步块 10(重量锁)重量锁指 自旋重试
执行同步块 01(无锁) 自旋重试
执行同步块 10(重量锁)重量锁指针 阻塞
。。。 。。。 。。。

细节说明

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

3 轻量级锁

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

3.1 流程分析

例假设有两个方法同步块,利用同一个对象加锁,注意是一个线程给对象加了两次锁

static final Object obj = new Object();
public static void method1() {
    
    
    synchronized( obj ) {
    
    
        // 同步块 A
        method2();
    }
}
public static void method2() {
    
    
    synchronized( obj ) {
    
    
        // 同步块 B
    }
}

3.1.1 加锁流程

  1. 创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word

    • lock record 存储锁定对象的 Mark Word
      在这里插入图片描述
  2. 让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录

    • 将所锁录中的记录和对象头markword的记录进行交换,00 和 01 进行互换,互换成功代表加锁成功
    • cas保证交换操作是原子的,不会被打断。
      在这里插入图片描述
  3. 如果 cas 替换成功,对象头中存储了锁记录地址和状态 00,表示由该线程给对象加锁,这时图示如下
    在这里插入图片描述

  4. 如果 cas 失败,有两种情况
    (1) 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
    (2) 如果是自己执行了 synchronized 锁重入( 同一个线程给同一个对象加了多次锁 ),那么再添加一条 Lock Record 作为重入的计数,如示例代码
    在这里插入图片描述

3.1.2 解锁流程

  1. 当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一

在这里插入图片描述

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

3.2 锁膨胀

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

3.2.1 锁膨胀流程

  1. 当Thread-1进行轻量级加锁时,Thread-0已经对该对象进行了加锁,此时Thread-1利用cas尝试将锁记录和对象的mark down交换就会失败
    在这里插入图片描述
  2. Thread-1加轻量级锁失败就会进入锁膨胀流程
    (1) 为Object对象申请Monitor重量级锁,让Object的mark word指向Monitor地址
    (2) 然后自己进入Monitor的EntryList BLOCKED
    在这里插入图片描述
  3. 当Thread-0推出同步代码块后,会使用cas将Mark Word的值恢复给对象头,但由于所膨胀,对象头的Mark Word已经指向Monitor的地址,并且锁状态为10,因此加锁失败。
  4. 这是就会进入重量级锁的解锁流程,即按照Mark Word的Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中的BLOCKED线程竞争锁。
    • 参考3.1

4 偏向锁

4.1 为什么需要偏向锁

  • 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。

    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
        }
    }
    
  • 进行了两次锁重入,每次锁重入依旧会尝试用 锁记录 替换 markword,会造成一定的损耗。相当于翻书包。
    在这里插入图片描述

4.2 偏向锁流程

  • Java 6 中引入了偏向锁来做进一步优化
  • 偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),因此,第一次交换时使用 CAS 将线程 ID 设置到对象的 Mark Word 头,如果交换成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁。
    在这里插入图片描述

4.3 偏向状态

  • 首先回顾一下对象头的Mark Word,这里以64位机器为例

    |-----------------------------------------------------------------------------------------------------------------|
    |                                             Object Header(128bits)                                              |
    |-----------------------------------------------------------------------------------------------------------------|
    |                                   Mark Word(64bits)               |  Klass Word(64bits)    |      State         |
    |-----------------------------------------------------------------------------------------------------------------|
    |    unused:25|identity_hashcode:31|unused:1|age:4|biase_lock:0| 01 | OOP to metadata object |      Nomal         |
    |-----------------------------------------------------------------------------------------------------------------|
    |    thread:54|      epoch:2       |unused:1|age:4|biase_lock:1| 01 | OOP to metadata object |      Biased        |
    |-----------------------------------------------------------------------------------------------------------------|
    |                        ptr_to_lock_record:62                 | 00 | OOP to metadata object | Lightweight Locked |
    |-----------------------------------------------------------------------------------------------------------------|
    |                       ptr_to_heavyweight_monitor:62          | 10 | OOP to metadata object | Heavyweight Locked |
    |-----------------------------------------------------------------------------------------------------------------|
    |                                                              | 11 | OOP to metadata object |    Marked for GC   |
    |-----------------------------------------------------------------------------------------------------------------|
    

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值

注意:

  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -
    XX:BiasedLockingStartupDelay=0 来禁用延迟

4.4 撤销偏向状态

4.4.1 情况一:调用对象hashCode()

  • 调用 hashCode() 会禁用 偏向锁,这是因为 markword 一共64位,使用偏向锁,Thread ID占54位,那么就没地方存放31位hashcode
    • 轻量级锁会在锁记录中记录 hashCode
    • 重量级锁会在 Monitor 中记录 hashCode

4.4.2 情况二:其它线程加锁对象

  • 如果存在其他线程加锁,那么偏向锁将膨胀为轻量级锁。

4.4.3 情况三:调用 wait/notify

  • 这个很好理解,因为wait/notify只有重量级锁才有

4.5 批量重偏向

  • 如果对象被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID
  • 当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程

4.5.1 代码示例

private static void test3() throws InterruptedException {
    
    
    Vector<Dog> list = new Vector<>();
    Thread t1 = new Thread(() -> {
    
    
        // 创建30个对象,并让它们偏向 thread1
        for (int i = 0; i < 30; i++) {
    
    
            Dog d = new Dog();
            list.add(d);
            synchronized (d) {
    
    
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
        }
        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).toPrintable());
            // 对30个对象重新加锁,升级为轻量级锁
            synchronized (d) {
    
    
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
        }
    }, "t2");
    t2.start();
}

4.5.2 代码结果分析

  1. 首先是t1给30个Dog对象都加锁30,可以看到,30个Dog对象的ThreadID都是t1的。
  2. 接着t2给30个Dog对象加锁
    • 0 ~ 18 加锁前对象头为偏向状态101,ThreadID为t1的,加锁后升级为轻量级锁,解锁后对象重置为不可偏向状态001
    • 到第19个对象,加锁前为偏向状态101,ThreadID是t1的,加锁时没有升级为轻量级锁,而是重置为偏向状态,并且ThreadID为t2的,此时对象偏向t2
    • 这是因为阈值达到20次,第21个对象的加锁前中后都是偏向状态101,并且threadID是t2的,对象从t1被 批量重偏向 到t2
[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 // 不可偏向状态 001
[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
// 19加锁前偏向状态101,threadID是t1的,加锁时没有升级为轻量级锁,而是重置为偏向状态
// 并且ThreadID为t2的,此时对象偏向t2
// 阈值达到20次,第21个对象的加锁前中后都是偏向状态101,并且threadID是t2的,从t1偏向为t2
[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

4.6 批量撤销

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

4.7 锁消除

public void b() throws Exception {
    
    
    Object o = new Object();
    synchronized (o) {
    
    
        x++;
    }
}
  • o对象不会被共享,synchronized没有任何意义,那么JIT即时编译器会把synchronized优化掉,实际执行时是没有对o对象加锁的

  • 锁消除是默认打开的,关闭操作如下:

    java -XX:-EliminateLocks -jar benchmarks.jar
    

4.8 锁粗化

在遇到一连串地对同一锁不断进行请求和释放的操作时,把所有的锁操作整合成锁的一次请求,从而减少对锁的请求同步次数,这个操作叫做锁的粗化。

5 小结

  • 偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。
  • 轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。
  • 重量级锁:有实际竞争,且锁竞争时间长。

猜你喜欢

转载自blog.csdn.net/qq_36389060/article/details/121721807