synchronized原理

一、Java 对象头

由于 Java 面向对象的思想,在 JVM 中需要大量存储对象,存储时为了实现一些额外的功能,需要在对象中添加一些标记字段用于增强对象功能,这些标记字段组成了对象头。

1、对象头形式

普通对象

 数组对象

2、对象头的组成

Mark Word

对象标记,这部分主要用来存储对象自身的运行时数据,如 hashcode、gc 分代年龄等。Mark Word 的位长度取决于 JVM,32 位的 JVM 的 Mark Word 为 32 位,64 位的 JVM 的 Mark Word 为64位。

为了让一个字大小存储更多的信息,JVM 将字的最低两个位设置为标记位,不同标记位下的 Mark Word示意如下:

其中各部分的含义如下:

lock: 2位的锁状态标记位

biased_locklock状态
001无锁
101偏向锁
000轻量级锁
010重量级锁
011GC标记
  • biased_lock:对象是否启用偏向锁标记,只占 1 个二进制位。为 1 时表示对象启用偏向锁,为 0 时表示对象没有偏向锁。
  • age:4位的 Java 对象年龄。在GC中,如果对象在 Survivor 区复制一次,年龄增加 1 。当对象达到设定的阈值时,将会晋升到老年代。由于age只有4位,所以最大值为15,
  • identity_hashcode:25 位的对象标识 Hash 码,当对象被锁定时,该值会移动到管程 Monitor 中。
  • thread:持有偏向锁的线程 ID。
  • epoch:偏向时间戳。
  • ptr_to_lock_record:指向栈中锁记录的指针。
  • ptr_to_heavyweight_monitor:指向管程 Monitor 的指针。

64 位下的标记字与 32 位的相似,不再赘述: 

Klass Word

这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM 通过这个指针确定对象是哪个类的实例。该指针的位长度为 JVM 的一个字大小,即 32 位的 JVM 为 32 位,64 位的 JVM 为 64 位。

array length

如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着 JVM 架构的不同而不同,32 位的 JVM 上,长度为 32 位;64 位 JVM 则为 64 位。

参考文章:Java对象头详解 - 简书 (jianshu.com)

二、Monitor 原理

Monitor 被翻译为监视器或管程

每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针。

Monitor 结构如下:

image-20220708173351715

刚开始 Monitor 中 Owner 为 null,当 Thread-2 执行 synchronized(obj) ,就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor 中只能有一个 Owner,在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入 EntryList 阻塞。Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争时是非公平的。

wait & notify原理

Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为阻塞状态, 阻塞和等待的线程都不占用 CPU 时间片。

  • 阻塞的线程会在 Owner 线程释放锁时唤醒
  • 等待的线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList 重新竞争

注意:synchronized 必须是进入同一个对象的 Monitor 才有上述的效果,不加 synchronized 的对象不会关联监视器,不遵从以上规则。 它们都是线程之间进行协作的手段,都属于 Object 对象的方法,必须获得此对象的锁,才能调用这几个方法。

三、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;)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
            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

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

四、synchronized 原理进阶

1、轻量级锁

使用场景:一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

轻量级锁语法仍然是 synchronized。

假设有两个方法同步块,利用同一个对象加锁 

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

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

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

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

4. 如果 CAS 失败,有两种情况

  • 其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程 
  • 自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数

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

6. 当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 CAS 将 Mark Word 的值恢复给对象头

  • 成功,则解锁成功
  • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程 

2、锁膨胀

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

1. 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

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

3. 当 Thread-0 退出同步块解锁时,使用 CAS 将 Mark Word 的值恢复给对象头会失败,这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中阻塞线程。

3、自旋优化

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

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

4、偏向锁

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

Java6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS,以后只要不发生竞争,这个对象就归该线程所有。

 

偏向状态

回忆一下对象头格式

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

1)测试延迟特性

查看对象头的信息需要导入jar包

        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.10</version>
        </dependency>

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

@Slf4j(topic = "c.TestBiased")
public class TestBiased {
    public static void main(String[] args) throws InterruptedException {
        log.debug(ClassLayout.parseInstance(new Dog()).toPrintable());
        Thread.sleep(4000);
        log.debug(ClassLayout.parseInstance(new Dog()).toPrintable());
    }
}
class Dog{
}

输出说明:前八段倒叙排列就是 Mark Word,后面方便观看,会去掉多余部分。

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           94 ff 00 f8 (10010100 11111111 00000000 11111000) (-134152300)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

结果:

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101

2)  测试偏向锁

考虑到有延迟特性,添加虚拟机参数。

-XX:BiasedLockingStartupDelay=0
@Slf4j(topic = "c.TestBiased2")
public class TestBiased2 {
    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();
        ClassLayout classLayout = ClassLayout.parseInstance(dog);
        log.debug(classLayout.toPrintable());
        synchronized (dog){
            log.debug(classLayout.toPrintable());
        }
        log.debug(classLayout.toPrintable());
    }
}
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101 
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101

注意:处于偏向锁的对象解锁后,线程 id 仍存储于对象头中 

3)测试禁用

在上面测试代码运行时添加 VM 参数,禁用偏向锁

 -XX:-UseBiasedLocking

输出:使用的是轻量级锁

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000 
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

4)测试 hashCode

正常状态对象一开始是没有 hashCode 的,第一次调用才生成。

虚拟机参数:

-XX:BiasedLockingStartupDelay=0
    Dog dog = new Dog();
    ClassLayout classLayout = ClassLayout.parseInstance(dog);
    log.debug(classLayout.toPrintable());
    dog.hashCode();
    log.debug(classLayout.toPrintable());
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
00000000 00000000 00000000 00010111 01100001 11101000 01000000 00000001

5)撤销 - 调用对象 hashCode 

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

  • 轻量级锁会在锁记录中记录 hashCode 
  • 重量级锁会在 Monitor 中记录 hashCode

hash 码 31 位,用的时候才会产生,默认为 0,当第一次调用 hashCode 方法时,才会产生这个对象的 hash 码, 并把它填充到对象的对象头中。

 虚拟机参数:

-XX:BiasedLockingStartupDelay=0
    Dog dog = new Dog();
    ClassLayout classLayout = ClassLayout.parseInstance(dog);
    log.debug(classLayout.toPrintable());

    dog.hashCode();//会禁用这个对象的偏向锁

    log.debug(classLayout.toPrintable());
    synchronized (dog){
        log.debug(classLayout.toPrintable()); //变成轻量级锁
    }
    log.debug(classLayout.toPrintable());
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001 
00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000 
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001

 6)撤销 - 其它线程使用对象

当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

虚拟机参数:

-XX:BiasedLockingStartupDelay=0
    Dog dog = new Dog();

    new Thread(() ->{
        synchronized (dog){
            log.debug(ClassLayout.parseInstance(dog).toPrintable());
        }
        synchronized (TestBiased.class){
            TestBiased.class.notify();
        }
    },"t1").start();

    new Thread(() ->{
        synchronized (TestBiased.class){
            try {
                TestBiased.class.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.debug(ClassLayout.parseInstance(dog).toPrintable());
        synchronized (dog){
            log.debug(ClassLayout.parseInstance(dog).toPrintable()); //变成轻量级锁
        }
        log.debug(ClassLayout.parseInstance(dog).toPrintable());
    },"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

7)撤销 - 调用 wait/notify

调用 wait/notify,也会撤销偏向锁,因为 wait/notify 只有重量级锁才有

 虚拟机参数:

-XX:BiasedLockingStartupDelay=0
    Dog d = new Dog();
    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").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 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程。

 虚拟机参数:

-XX:BiasedLockingStartupDelay=0
    Vector<Dog> list = new Vector();
    //先让偏向锁偏向线程1
    new Thread(() -> {
        for (int i = 0;i < 30;i++){
            Dog dog = new Dog();
            list.add(dog);
            synchronized (dog){
                log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
            }
        }
        synchronized (list){
            list.notify();
        }
    },"t1").start();

    //线程二改变偏向锁指向30次
    new Thread(() -> {
        synchronized (list){
            try {
                list.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.debug("===============> ");
        for (int i = 0;i < 30;i++){
            Dog dog = list.get(i);
            log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
            synchronized (dog){
                log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
            }
            log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
        }
    },"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 会觉得自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。

注意:偏向指类偏向,而不是对象实例偏向,一个类只能有一个偏向。

 虚拟机参数: 

-XX:BiasedLockingStartupDelay=0
    static Thread t1,t2,t3;
    public static void main(String[] args) throws InterruptedException {
        Vector<Dog> list = new Vector();
        int loopNumber = 39;
        //先让偏向锁偏向线程1
        t1 = new Thread(() ->{
            for(int i = 0; i < loopNumber;i++){
                Dog dog = new Dog();
                list.add(dog);
                synchronized (dog){
                    log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
                }
            }
            LockSupport.unpark(t2);
        },"t1");
        t1.start();

        //线程二改变偏向锁指向40次
        t2 = new Thread(() -> {
            LockSupport.park();
            System.out.println("=========>");
            for(int i = 0;i < loopNumber;i++){
                Dog dog = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
                synchronized (dog){
                    log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
            }
            LockSupport.unpark(t3);
        },"t2");
        t2.start();

        //之后线程三再去改变偏向锁,此时就会变成轻量级锁,无论再改变几次
        t3 = new Thread(() -> {
            LockSupport.park();
            System.out.println("=========>");
            for(int i = 0;i < loopNumber;i++){
                Dog dog = list.get(i);
                synchronized (dog){
                    log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
            }
        },"t3");
        t3.start();
        t3.join();
        log.debug(ClassLayout.parseInstance(new Dog()).toPrintable());

5、锁消除

JIT 编译器在编译的时候,进行逃逸分析。分析 synchronized 锁对象是不是只可能被一个线程加锁,不存在其他线程来竞争加锁的情况。这时就可以消除该锁了,提升执行效率。编译就不用加入 monitorenter 和 monitorexit 指令。

public static void main(String[] args) { 
    int size = 10000;
    List<String> list = new ArrayList();
    for (int i = 0; i < size; i++) {
        list.add(appendStr("hi", i));
    } 
}
public static String appendStr(String str, int i) {
    StringBuffer sb= new StringBuffer();
    sb.append(str);
    sb.append(i);
    return sb.toString();
}

StringBuffer 的 append 为同步方法。但上述的 appendStr 中的 sb 对象没有传递到方法外,不会被其他线程引用,不存在锁竞争的情况,因此可以进行锁消除。

6、锁粗化

JIT编译时,发现一段代码中频繁的加锁释放锁,会将前后的锁合并为一个锁,避免频繁加锁释放锁。

synchronized(this){
}
synchronized(this){
}
synchronized(this){
}

版权声明:本文为qq_51409098原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。