synchronized

synchronized

一、概述

  • synchronized的作用:
    • 保证在同一时刻,只有一个线程可以执行某个方法或某个代码块。
    • 保证 原子性可见性
  • 同步的前提:
    • 两个或者两个以上的线程
    • 多个线程使用同一个锁

二、synchronized的使用

1、同步代码块

需要显示指定锁对象(可以是任意对象),进入同步代码块前,要先获取指定的锁对象

public class SynchronizedCodeBlock {

    static int number = 100;

    public static void main(String[] args) {
        Object lock = new Object();
        new Thread(()-> {
            // 同步代码块开始,获取锁(lock对象)
            synchronized (lock) {
                number--;
            }
            // 同步代码块结束,释放锁(lock对象)
        }).start();
    }

}

2、同步方法(非静态)

不需要指定锁对象(锁是 当前实例对象,即 this),进入 同步方法 前,要先获取对应的锁对象。

public class SynchronizedMethod {

    static int number = 100;

    public static void main(String[] args) {
        SynchronizedMethod synchronizedMethod = new SynchronizedMethod();
        // 同步方法开始,获取锁(synchronizedMethod对象)
        synchronizedMethod.syncMethod();
        // 同步方法结束,释放锁(synchronizedMethod对象)
    }

    public synchronized void syncMethod() {
        number--;
    }

}

3、同步方法(静态)

不需要指定锁对象(锁是 当前类的Class对象),进入 静态同步方法 前,要先获取对应的锁对象。

public class StaticSynchronizedMethod {

    static int number = 100;

    public static void main(String[] args) {
        // 静态同步方法开始,获取锁(StaticSynchronizedMethod类)
        StaticSynchronizedMethod.staticSyncMethod();
        // 静态同步方法结束,释放锁(StaticSynchronizedMethod类)
    }

    public static synchronized void syncMethod() {
        number--;
    }

}

三、Monitor 监视器

1、Monitor 是什么

Monitor 称作 监视器/管程,是由操作系统提供的对象

  • 每个Java对象都可以关联一个Monitor
  • 如果用synchronized给对象上锁(重量级锁),该对象头的Mark Word中就会记录指向Monitor对象的指针。

2、工作原理

在这里插入图片描述

关于 Owner

  • 每个锁对象都会关联一个Monitor,一个Monitor同一时刻只能有一个Owner
  • 刚开始时,Monitor 中的 Ownernull
  • 线程开始执行 synchronized 代码块/方法,如果获取锁成功,则将 Owner 设置为 获取锁的线程
  • 线程执行完 synchronized 代码块/方法,释放锁,则将 Owner 设置为 null

关于 EntryList

  • 竞争锁失败的线程,会变成 BLOCKED 状态,放到 EntryList 中。
  • 占据锁的线程释放锁后,EntryListBLOCKED 的线程就会被唤醒来竞争锁(竞争是非公平的

关于 WaitSet

  • 获取了锁之后进入 WAITING 状态的线程,会放到 WaitSet 中。

    例如:Owner 线程调用 wait 方法,变为 WAITING 状态,进入 WaitSet

  • WaitSetWAITING状态的线程被唤醒后,可以进入EntryList 重新竞争锁(注意:这里唤醒后不是立即获取锁!

    例如:其他线程 调用 notify 或 notifyAll 唤醒 WaitSetWAITING状态的线程

注意:

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

3、字节码演示

JVM是通过 进入对象监视器(monitorenter) 和 退出对象监视器(monitorexit)来实现对方法、同步块的同步的。

  • 在 同步方法调用之前 加入一个 monitorenter 指令
  • 在 退出方法和异常处 加入一个 monitorexit 指令。

本质就是获取对象监视器Monitor,而这个获取过程具有排他性,从而达到了同一时刻只能一个线程访问的目的。

没有获取到锁的线程,将会阻塞在方法的入口处,直到获取锁的线程 monitorexit 后才能尝试继续获取锁。

public class MonitorDemo {
    static final Object lock = new Object();
    static int counter = 0;

    public static void main(String[] args) {
        synchronized (lock) {
            counter++;
        }
    }
}

对应的字节码(命令:javap -p -c MonitorDemo.class

public class thread.MonitorDemo {
    
  // ....
    
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // 获取lock引用(synchronized开始)
       3: dup								// 复制lock引用
       4: astore_1							// 存储lock引用 -> slot 1
       5: monitorenter						// 将 lock对象 MarkWord 置为 Monitor 指针
           
       /* synchronized代码块内容 */    
       6: getstatic     #3                  // <- counter
       9: iconst_1							// 准备常数0
      10: iadd								// +1
      11: putstatic     #3                  // -> counter

      14: aload_1							// 载入lock引用 <- slot 1
      15: monitorexit						// 将 lock对象 MarkWord 重置,唤醒 EntryList
      16: goto          24					// 跳转到24行
          
      /* 异常处理指令(出现异常,也能自动地释放锁) */    
      19: astore_2							// 存储异常e -> slot 2
      20: aload_1							// 载入lock引用 <- slot 1
      21: monitorexit						// 将 lock对象 MarkWord 重置,唤醒 EntryList
      22: aload_2							// 载入异常e <- slot 2
      23: athrow							// throw e
      24: return
          
    /* 异常处理 */        
    Exception table:
       from    to  target type
           6    16    19   any				// 6~16行出现异常,跳转到19行
          19    22    19   any
   // ....        
}

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

四、synchronized 锁分类

Java对象头Mark Work 中包含了 的信息(下面是以32位虚拟机为例,64位虚拟机也差不多)

在这里插入图片描述

1、重量级锁

Monitor 监视器 就属于重量级锁

重量级锁的 Mark Work:

  • ptr_to_heavyweight_monitor:指向重量级锁的指针
  • 10:锁标志位,表示是重量级锁

2、轻量级锁

如果一个对象虽然有多个线程访问,但是多线程访问的时间是错开的(也就是没有竞争),可以使用轻量级锁优化。

  • 轻量级锁对使用者是透明的(即语法仍然是synchronized
  • 如果有锁竞争,轻量级锁会升级为重量级锁

3、偏向锁

Tips:关于偏向锁的说明可能会不太理解,为了结构清晰先提一嘴,先知道有这么个东西,后面有详细说。

轻量级锁在每次重入时,CAS都会失败,但是还是无法避免进行CAS。

Java 6 中引入了偏向锁进行优化:

  • 第一次进行 CAS 时,将 thread 设置到 对象头的 Mark Word 中。
  • 之后每次获取锁,只要 thread 和 Mark Word 中的一致,就表示没有竞争不用再次 CAS

五、加锁流程分析

这里主要分析的是 轻量级锁 的加锁流程

1、加锁流程

执行 synchronized 代码块/方法时,会在线程的栈帧中创建轻量级锁记录 Lock Record

  • lock record地址
  • 00:锁标志位,表示是轻量级锁
  • Object reference:用于存储锁对象的引用

在这里插入图片描述

然后做两件事

  • Object reference 指向 锁对象 Object
  • 采用 CAS 替换 lock record地址 + 锁标志位00锁对象 Object 的 Mark Word

在这里插入图片描述

CAS 替换成功,如下图所示

在这里插入图片描述

CAS 替换失败,有两种情况:

  • 情况1:其它线程已经持有了Object轻量级锁(锁竞争),进入锁膨胀过程。
  • 情况2:自己已经持有了Object轻量级锁(重入),添加一条锁信息为 null 的 Lock Record,作为重入的计数(如下图)

在这里插入图片描述

synchronized 代码块/方法结束,如果有锁信息为 null 的 Lock Record(有重入),优先清除该Lock Record,重入计数减1

在这里插入图片描述

如果没有锁信息为 null 的 Lock Record(没有重入),则用 CAS 将 Mark Word 的值恢复给 锁对象 Object

  • 成功,则解锁成功(00 轻量级锁 —> 01 无锁)
  • 失败,则说明轻量级锁 已经升级为重量级锁,进入重量级锁的解锁流程。

2、锁膨胀(轻量级锁 -> 重量级锁)

加锁流程中,如果其他线程已经持有了轻量级锁(锁竞争),会导致CAS替换失败

  • 此时就要进行锁膨胀:将轻量级锁升级为重量级锁

当 Thread-1 获取轻量级锁时,发现 Thread-0 已经持有了轻量级锁

在这里插入图片描述

此时发生了锁竞争,Thread-1 加轻量级锁失败,进入锁膨胀流程:

  1. 锁对象 Object轻量级锁 升级为 重量级锁

    lock record地址 + 锁标志位00 —> Monitor地址 + 锁标志位10

  2. Monitor 的 Owner 指向 原来已经持有轻量级锁的 Thread-0

  3. Thread-1 变成 BLOCKED 状态,进入 Monitor 的 EntryList

在这里插入图片描述

当 Thread-0 结束 synchronized 同步块/方法,发现没有重入,则用 CAS 将 Mark Word 的值恢复给 锁对象 Object

此时,由于锁膨胀锁对象 Object已经从 轻量级锁 升级为 重量级锁,所以 CAS 失败,进入重量级锁的解锁流程。

3、重量级锁的解锁流程

  1. 根据 Monitor的地址,找到 Monitor 对象
  2. 将 Owner 设置为 null
  3. 唤醒 EntryList 中 BLOCKED 的 线程

4、自旋的优化

竞争重量级锁的时候,可以使用自旋来进行优化

  • 如果当前线程自旋成功(即在自旋时持锁的线程释放了锁),那么当前线程就可以避免阻塞(不用进行上下文切换)就获得了锁

自旋重试成功的情况

线程 1 (core 1 上)锁标志位线程 2 (core 2 上)
-10(重量锁)-
访问同步块,获取 monitor10(重量锁)-
成功(加锁)10(重量锁)-
执行同步块10(重量锁)-
执行同步块10(重量锁)访问同步块,获取 monitor
执行同步块10(重量锁)自旋重试
执行完毕10(重量锁)自旋重试
成功(解锁)01(无锁)自旋重试
-10(重量锁)成功(加锁)
-10(重量锁)执行同步块
-

自旋重试失败的情况:自旋了一定次数,还是没有等到持锁的线程释放锁

线程 1(core 1 上)锁标志位线程 2(core 2 上)
-10(重量锁)-
访问同步块,获取 monitor10(重量锁)-
成功(加锁)10(重量锁)-
执行同步块10(重量锁)-
执行同步块10(重量锁)访问同步块,获取 monitor
执行同步块10(重量锁)自旋重试
执行同步块10(重量锁)自旋重试
执行同步块10(重量锁)自旋重试
执行同步块10(重量锁)阻塞

注意事项:

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。

  • Java 6 之后自旋锁是自适应的:

    如果上一次自旋操作成功过,那么认为这一次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋。

  • Java 7 之后不能控制是否开启自旋功能

5、锁消除

动态编译同步块时,JIT 编译器可以借助逃逸分析,判断synchronized锁对象是不是只可能被一个线程加锁,不存在其他线程来竞争加锁的情况。如果是,JIT 编译器在编译的时候就会取消对这部分代码的同步,这个过程就叫同步省略,也叫锁消除

  • 参数-XX:+EliminateLocks:开启同步替换(默认打开)。

线程同步的代价是相当高的,同步的后果是降低并发性和性能。同步省略可以大大提高并发性和性能。

public void eliminateLocks() {
    Object obj = new Object();
    synchronized(obj) {
        System.out.println(obj);
    }
}

上述代码中,对 obj 对象加锁,但是obj 对象的生命周期只在方法内,不会逃逸,所以在 JIT 编译阶段就会把锁优化掉,如下所示:

public void eliminateLocks() {
    Object obj = new Object();
    System.out.println(obj);
}

6、锁粗化

JIT编译时,发现一段代码中对相同对象多次加锁,导致线程发生多次重入,会合并为一个锁,避免频繁加锁释放锁。如:

Object obj = new Object();
synchronized(obj) {逻辑1;}
synchronized(obj) {逻辑2;}
synchronized(obj) {逻辑3;}
Object obj = new Object();
synchronized(obj) {
    逻辑1;
    逻辑2;
    逻辑3;
}

六、偏向锁

1、基本概念

轻量级锁在每次重入时,CAS都会失败,但是还是无法避免进行CAS。

在这里插入图片描述

Java 6 中引入了偏向锁进行优化:

  • 第一次进行 CAS 时,将 thread 设置到 对象头的 Mark Word 中。
  • 之后每次获取锁,只要 thread 和 Mark Word 中的一致,就表示没有竞争不用再次 CAS

在这里插入图片描述

注意:这里偏向锁中的 ThreadIdgetId() 获取的 线程ID 不一样。

2、偏向锁的Mark Word

在这里插入图片描述

3、开启与延迟

偏向锁默认是开启的

  • 对象创建后,Mark Word 最后3位为 1 01,这时 thread、epoch、age 都是0。(加锁时才会设置值)

偏向锁默认是延迟的(大概4秒),不会在程序启动的时候立刻生效

  • 可以添加JVM参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟

可以引入以下maven依赖后打印观察一下

<!-- 打印 Java 对象内存布局 -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

默认情况(开启 + 延迟)

public class BiasedLockDelayTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable()); // 0 01 无锁
        TimeUnit.SECONDS.sleep(4);
        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable()); // 1 01 偏向锁
    }
}
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)	// 0 01 无锁
  8   4        (object header: class)    0x00000f28
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)	// 1 01 偏向锁
  8   4        (object header: class)    0x00000f28
 12   4        (object alignment gap)    
Instance size: 16 bytes

禁用延迟(开启 + 无延迟)

/**
 * VM options:-XX:BiasedLockingStartupDelay=0
 */
public class BiasedLockNoDelayTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable()); // 1 01 偏向锁
    }
}
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0) // 1 01 偏向锁
  8   4        (object header: class)    0x00000f28
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

4、禁用

可以通过 -XX:-UseBiasedLocking禁用偏向锁

  • 对象创建后,Mark Word 最后三位的值是 0 01,这时 hashcode、age 都是0。
  • 禁用偏向锁后,优先使用轻量级锁

示例:禁用偏向锁 + 禁用延迟

/**
 * VM options:-XX:BiasedLockingStartupDelay=0 -XX:-UseBiasedLocking
 */
public class DisableBiasedLockTest {

    public static void main(String[] args) {
        Object obj = new Object();
        ClassLayout classLayout = ClassLayout.parseInstance(obj);

        new Thread(() -> {
            System.out.println("synchronized 前:");
            System.out.println(classLayout.toPrintable());

            synchronized (obj) {
                System.out.println("synchronized 中:");
                System.out.println(classLayout.toPrintable());
            }

            System.out.println("synchronized 后:");
            System.out.println(classLayout.toPrintable());
        }).start();

    }

}
synchronized 前:
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0) // 0 01 无锁
synchronized 中:
  0   8        (object header: mark)     0x000000017007a950 (thin lock: 0x000000017007a950) // 00 轻量级锁
synchronized 后:
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0) // 0 01 无锁

5、撤销 - 调用对象hashCode

调用对象的hashcode方法时,会撤销这个对象的偏向状态

  • 原因:偏向锁的 Mark Word 存储的是 thread,没有位置存储 hashcode 的值了

为什么轻量级锁和重量级锁没有这个问题?

  • 轻量级锁:会在 Lock Record 中存储 hashCode
  • 重量级锁:会在 Monitor 中存储 hashCode

【案例】

调用hashCode之前

/**
 * VM options:-XX:BiasedLockingStartupDelay=0
 */
public class RevokeBiasedLockTest1 {
    public static void main(String[] args) {
        Object obj = new Object();
        ClassLayout classLayout = ClassLayout.parseInstance(obj);

        new Thread(() -> {
            System.out.println("synchronized 前:");
            System.out.println(classLayout.toPrintable());	// 1 01 偏向锁 

            synchronized (obj) {
                System.out.println("synchronized 中:");
                System.out.println(classLayout.toPrintable()); // 1 01 偏向锁
            }

            System.out.println("synchronized 后:");
            System.out.println(classLayout.toPrintable()); // 1 01 偏向锁
        }).start();
    }
}

调用hashCode之后,偏向锁被撤销,升级为轻量级锁

/**
 * VM options:-XX:BiasedLockingStartupDelay=0
 */
public class RevokeBiasedLockTest1 {
    public static void main(String[] args) {
        Object obj = new Object();
        ClassLayout classLayout = ClassLayout.parseInstance(obj);

        new Thread(() -> {
            obj.hashCode(); // 使用对象的hashCode
            
            System.out.println("synchronized 前:");
            System.out.println(classLayout.toPrintable());	// 0 01 无锁 

            synchronized (obj) {
                System.out.println("synchronized 中:");
                System.out.println(classLayout.toPrintable()); // 00 轻量级锁
            }

            System.out.println("synchronized 后:");
            System.out.println(classLayout.toPrintable()); // 0 01 无锁 
        }).start();
    }
}

6、撤销 - 其他线程使用锁对象

其他线程使用锁对象,偏向锁被撤销,升级为轻量级锁

/**
 * VM options:-XX:BiasedLockingStartupDelay=0
 */
public class RevokeBiasedLockTest2 {

    public static void main(String[] args) {
        Object obj = new Object();
        ClassLayout classLayout = ClassLayout.parseInstance(obj);

        new Thread(() -> {
            System.out.println("t1线程执行了:");

            System.out.println("synchronized 前:");
            System.out.println(classLayout.toPrintable());  // 1 01 偏向锁

            synchronized (obj) {
                System.out.println("synchronized 中:");
                System.out.println(classLayout.toPrintable()); // 1 01 偏向锁
            }

            System.out.println("synchronized 后:");
            System.out.println(classLayout.toPrintable()); // 1 01 偏向锁

            synchronized (RevokeBiasedLockTest2.class) {
                RevokeBiasedLockTest2.class.notify();
            }

        }, "t1").start();

        new Thread(() -> {
            // 等t1线程执行完再执行
            synchronized (RevokeBiasedLockTest2.class) {
                try {
                    RevokeBiasedLockTest2.class.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

            System.out.println("t2线程执行了");

            System.out.println("synchronized 前:");
            System.out.println(classLayout.toPrintable());  // 1 01 偏向锁

            // 这里t2线程使用了相同的锁对象,「偏向锁」撤销,升级为「轻量级锁」
            synchronized (obj) {
                System.out.println("synchronized 中:");
                System.out.println(classLayout.toPrintable()); // 00 轻量级锁
            }

            System.out.println("synchronized 后:");
            System.out.println(classLayout.toPrintable()); // 0 01 无锁
        }, "t2").start();

    }
}

7、撤销 - 使用wait/notify

使用wait/notify(涉及到Monitor),偏向锁被撤销,升级为重量级锁

/**
 * VM options:-XX:BiasedLockingStartupDelay=0
 */
public class RevokeBiasedLockTest3 {

    public static void main(String[] args) {
        Object obj = new Object();
        ClassLayout classLayout = ClassLayout.parseInstance(obj);

        new Thread(() -> {
            System.out.println("t1线程执行了:");

            System.out.println("synchronized 前:");
            System.out.println(classLayout.toPrintable());  // 1 01 偏向锁

            synchronized (obj) {

                System.out.println("synchronized 中1:");
                System.out.println(classLayout.toPrintable()); // 1 01 偏向锁

                try {
                    obj.wait(2);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                System.out.println("synchronized 中2:");
                System.out.println(classLayout.toPrintable()); // 10 重量级锁
            }

            System.out.println("synchronized 后:");
            System.out.println(classLayout.toPrintable()); // 10 重量级锁


        }, "t1").start();

    }
}
synchronized 前:
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
synchronized10   8        (object header: mark)     0x00000001558be805 (biased: 0x00000000005562fa; epoch: 0; age: 0)
synchronized20   8        (object header: mark)     0x0000000155833812 (fat lock: 0x0000000155833812)
synchronized 后:
  0   8        (object header: mark)     0x0000000155833812 (fat lock: 0x0000000155833812)

8、批量重偏向

  • 重偏向的概念

    撤销是很消耗性能的,而重偏向是对撤销对一个优化。

    之前已经举例说明了,如果有其他线程使用锁对象,偏向锁会被撤销,升级为轻量级锁

    但是,如果多个线程之间没有竞争,那么,有可能不需要撤销偏向锁,而是进行重偏向(重置锁对象的thread)

  • 重偏向的条件

    偏向锁撤销的次数达到阈值(默认为20),并且没有锁竞争,就会将 撤销偏向锁 的动作改为 重偏向

  • 重偏向的阈值

    默认为20,可以通过JVM参数 -XX:BiasedLockingBulkRebiasThreshold=20 设置。

【举例说明】

/**
 * VM options:-XX:BiasedLockingStartupDelay=0
 */
@Slf4j
public class RebiasTest {

    public static void main(String[] args) {
        Vector<Object> lockVector = new Vector<>();

        new Thread(() -> {
            for (int i = 0; i < 30; i++) {
                Object lock = new Object();
                lockVector.add(lock);
                synchronized (lock) {
                    System.out.println("[t1] - [" + i + "]");
                    System.out.println(ClassLayout.parseInstance(lock).toPrintable());
                }
            }

            synchronized (lockVector) {
                lockVector.notify();
            }
        }, "t1").start();

        new Thread(() -> {
            synchronized (lockVector) {
                try {
                    lockVector.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

            for (int i = 0; i < 30; i++) {
                Object lock = lockVector.get(i);
                System.out.println("[t2] - [" + i + "] - [before]");
                System.out.println(ClassLayout.parseInstance(lock).toPrintable());
                synchronized (lock) {
                    System.out.println("[t2] - [" + i + "] - [in]");
                    System.out.println(ClassLayout.parseInstance(lock).toPrintable());
                }
                System.out.println("[t2] - [" + i + "] - [after]");
                System.out.println(ClassLayout.parseInstance(lock).toPrintable());
            }
        }, "t2").start();

    }

}

这里截取部分打印结果看一下

[t1] - [0]
0   8        (object header: mark)     0x0000000126857805 (biased: 0x000000000049a15e; epoch: 0; age: 0)
[t1] - [1]
0   8        (object header: mark)     0x0000000126857805 (biased: 0x000000000049a15e; epoch: 0; age: 0)
// ......
[t1] - [29]
0   8        (object header: mark)     0x0000000126857805 (biased: 0x000000000049a15e; epoch: 0; age: 0)

// t2线程前19次,撤销偏向锁,升级为轻量级锁
[t2] - [0] - [before]
0   8        (object header: mark)     0x0000000126857805 (biased: 0x000000000049a15e; epoch: 0; age: 0)
[t2] - [0] - [in]
0   8        (object header: mark)     0x000000016e31a940 (thin lock: 0x000000016e31a940)
[t2] - [0] - [after]
0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
// ......
[t2] - [18] - [before]
0   8        (object header: mark)     0x0000000126857805 (biased: 0x000000000049a15e; epoch: 0; age: 0)
[t2] - [18] - [in]
0   8        (object header: mark)     0x000000016e31a940 (thin lock: 0x000000016e31a940)
[t2] - [18] - [after]
0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
// t2线程从20次开始,不再撤销偏向锁,而是重偏向-重置了thread(可以看到前面的位数发生了变化)
[t2] - [19] - [before]
0   8        (object header: mark)     0x0000000126857805 (biased: 0x000000000049a15e; epoch: 0; age: 0)
[t2] - [19] - [in]         VALUE
0   8        (object header: mark)     0x000000012685a905 (biased: 0x000000000049a16a; epoch: 0; age: 0)
[t2] - [19] - [after]
0   8        (object header: mark)     0x000000012685a905 (biased: 0x000000000049a16a; epoch: 0; age: 0)
// ......
[t2] - [29] - [before]
0   8        (object header: mark)     0x0000000126857805 (biased: 0x000000000049a15e; epoch: 0; age: 0)
[t2] - [29] - [in]         VALUE
0   8        (object header: mark)     0x000000012685a905 (biased: 0x000000000049a16a; epoch: 0; age: 0)
[t2] - [29] - [after]
0   8        (object header: mark)     0x000000012685a905 (biased: 0x000000000049a16a; epoch: 0; age: 0)

9、批量撤销

  • 批量撤销的概念:

    偏向锁撤销的次数达到阈值(默认为20),并且没有锁竞争,就会将 撤销偏向锁 的动作改为 重偏向

    但是如果重偏向之后的锁对象又被第三个线程使用,那么此时不是直接重偏向到第三个对象,而是 撤销偏向锁

    也就是说,偏向锁撤销的次数会超过20次

    而批量撤销就是,偏向锁撤销的次数会超过阈值(默认为40),这个类的所有对象都会变为不可偏向的(包括新建的)

  • 批量撤销的阈值:

    默认为40,可以通过JVM参数-XX:BiasedLockingBulkRevokeThreshold=40 设置

【举例说明】

按照线程 t1、t2、t3的顺序执行,t1线程前19个会发生撤销;

/**
 * 偏向锁_批量撤销
 * VM options:-XX:BiasedLockingStartupDelay=0
 */
public class BatchRevokeBiasedLockTest {

    static Thread t1, t2, t3;

    public static void main(String[] args) throws InterruptedException {
        Vector<Object> lockVector = new Vector<>();
        int loopNumber = 39;

        t1 = new Thread(() -> {
            for (int i = 0; i < loopNumber; i++) {
                Object lock = new Object();
                lockVector.add(lock);
                synchronized (lock) {
                    System.out.println("[t1] - [" + i + "]");
                    System.out.println(ClassLayout.parseInstance(lock).toPrintable());
                }
            }

            LockSupport.unpark(t2);
        }, "t1");
        t1.start();

        t2 = new Thread(() -> {
            LockSupport.park();

            for (int i = 0; i < loopNumber; i++) {
                Object lock = lockVector.get(i);
                System.out.println("[t2] - [" + i + "] - [before]");
                System.out.println(ClassLayout.parseInstance(lock).toPrintable());
                synchronized (lock) {
                    System.out.println("[t2] - [" + i + "] - [in]");
                    System.out.println(ClassLayout.parseInstance(lock).toPrintable());
                }
                System.out.println("[t2] - [" + i + "] - [after]");
                System.out.println(ClassLayout.parseInstance(lock).toPrintable());
            }

            LockSupport.unpark(t3);
        }, "t2");
        t2.start();

        t3 = new Thread(() -> {
            LockSupport.park();

            for (int i = 0; i < loopNumber; i++) {
                Object lock = lockVector.get(i);
                System.out.println("[t3] - [" + i + "] - [before]");
                System.out.println(ClassLayout.parseInstance(lock).toPrintable());
                synchronized (lock) {
                    System.out.println("[t3] - [" + i + "] - [in]");
                    System.out.println(ClassLayout.parseInstance(lock).toPrintable());
                }
                System.out.println("[t3] - [" + i + "] - [after]");
                System.out.println(ClassLayout.parseInstance(lock).toPrintable());
            }
        }, "t3");
        t3.start();

        t3.join();

        System.out.println("批量撤销后,这个类的所有对象都会变为不可偏向的");
        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());

    }

}
// 前面的打印信息这里就不展示了
....
批量撤销后,这个类的所有对象都会变为不可偏向的
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)	// 0 01 无锁
  8   4        (object header: class)    0x00000f28
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

七、小结

偏向锁轻量级锁重量级锁是 Java 中用于实现线程同步的三种不同级别的锁。

  1. 偏向锁(Biased Locking)
    • 当一个线程访问同步代码块并获取锁时,JVM 会假设这个线程会继续访问该代码块,因此会将锁标记为偏向该线程。
    • 如果其他线程试图访问同步块,偏向锁会被撤销,线程重新竞争锁。
    • 目标是在无竞争情况下,尽量减少同步的开销。
    • 适用于只有一个线程访问同步块的场景。
  2. 轻量级锁(Lightweight Locking)
    • 当多个线程使用同一个锁,但是不存在竞争时,JVM 会将锁标记为轻量级锁。
    • 使用 CAS 操作来尝试获取锁。如果获取成功,线程可以进入临界区执行;否则,进入自旋等待状态。
    • 自旋等待的目标是等待其他线程释放锁,避免线程切换的开销。
    • 适用于多个线程访问同步代码块但是没有锁竞争的情况。
  3. 重量级锁(Heavyweight Locking)
    • 当自旋等待无效或超过一定次数导致CAS失败时,JVM 会将轻量级锁膨胀为重量级锁。
    • 重量级锁会将竞争失败的线程阻塞,降低 CPU 的占用率。
    • 线程被阻塞时会进入阻塞队列,等待其他线程释放锁。
    • 适用于长时间或高并发情况下的同步场景。

这些锁的选择取决于同步代码块的竞争程度、持有锁的时间以及系统的并发性能需求。

  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

scj1022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值