讲讲synchronized的锁优化?

synchronized 是 Java 中用于实现线程同步的关键字,但在早期版本中,它的性能较差,主要是因为锁的实现是基于操作系统的互斥量(Mutex),会导致线程频繁地在用户态和内核态之间切换。为了提升性能,JVM 对 synchronized 进行了多种优化,主要包括 锁升级锁消除锁粗化自适应自旋锁 等。以下是这些优化的详细说明:


1. 锁升级(锁膨胀)

锁升级是 JVM 对 synchronized 锁的优化机制,根据锁的竞争情况动态调整锁的级别,从低开销的锁逐步升级为高开销的锁。锁升级分为以下几个阶段:

(1)无锁状态

  • 对象刚创建时,没有任何线程持有锁。

(2)偏向锁(Biased Locking)

  • 适用场景:只有一个线程访问同步块。
  • 实现原理
    • 在对象头中记录线程 ID,表示该线程持有锁。
    • 如果同一线程再次访问同步块,无需加锁,直接进入。
  • 优点:减少无竞争情况下的锁开销。

(3)轻量级锁(Lightweight Locking)

  • 适用场景:多个线程交替访问同步块,但没有竞争。
  • 实现原理
    • 使用 CAS(Compare-And-Swap)操作将对象头替换为指向线程栈中锁记录的指针。
    • 如果 CAS 成功,表示获取锁;如果失败,表示有竞争,升级为重量级锁。
  • 优点:减少线程阻塞和上下文切换的开销。

(4)重量级锁(Heavyweight Locking)

  • 适用场景:多个线程竞争同一锁。
  • 实现原理
    • 使用操作系统的互斥量(Mutex)实现锁。
    • 未获取锁的线程会被阻塞,进入等待队列。
  • 优点:保证线程安全,但开销较大。

2. 锁消除(Lock Elimination)

锁消除是 JVM 在编译时对代码进行优化,去除不必要的锁。

(1)适用场景

  • 锁对象是局部变量,且不会被其他线程访问。
  • 锁保护的代码块没有共享数据竞争。

(2)实现原理

  • JVM 通过逃逸分析(Escape Analysis)判断锁对象是否可能被其他线程访问。
  • 如果锁对象不会逃逸出当前方法,JVM 会消除锁。

(3)示例

public void method() {
    Object lock = new Object();  // 局部变量,不会被其他线程访问
    synchronized (lock) {       // 锁会被消除
        System.out.println("Hello");
    }
}

3. 锁粗化(Lock Coarsening)

锁粗化是 JVM 将多个连续的锁操作合并为一个更大的锁操作,减少锁的获取和释放次数。

(1)适用场景

  • 在循环或频繁的锁操作中,锁的获取和释放会导致性能下降。

(2)实现原理

  • JVM 检测到连续的锁操作时,会将它们合并为一个更大的锁操作。

(3)示例

public void method() {
    synchronized (this) {
        System.out.println("Operation 1");
    }
    synchronized (this) {
        System.out.println("Operation 2");
    }
    // 锁粗化后:
    synchronized (this) {
        System.out.println("Operation 1");
        System.out.println("Operation 2");
    }
}

4. 自适应自旋锁(Adaptive Spinning)

自旋锁是线程在获取锁失败时,不立即阻塞,而是循环尝试获取锁。自适应自旋锁会根据历史数据动态调整自旋次数。

(1)适用场景

  • 锁竞争不激烈,且线程持有锁的时间较短。

(2)实现原理

  • JVM 根据锁的历史获取情况,动态调整自旋次数。
  • 如果锁经常被成功获取,则增加自旋次数;否则,减少自旋次数。

(3)优点

  • 减少线程阻塞和上下文切换的开销。

5. 偏向锁撤销(Biased Lock Revocation)

当偏向锁的持有线程不再访问同步块时,JVM 会撤销偏向锁,恢复到无锁状态或升级为轻量级锁。

(1)适用场景

  • 偏向锁的持有线程长时间未访问同步块。

(2)实现原理

  • JVM 检测到偏向锁的持有线程不再活跃时,撤销偏向锁。

6. 总结

JVM 对 synchronized 的锁优化主要包括:

  1. 锁升级:根据锁的竞争情况,从偏向锁升级到轻量级锁,再到重量级锁。
  2. 锁消除:通过逃逸分析去除不必要的锁。
  3. 锁粗化:将多个连续的锁操作合并为一个更大的锁操作。
  4. 自适应自旋锁:根据历史数据动态调整自旋次数。
  5. 偏向锁撤销:当偏向锁不再需要时,撤销偏向锁。

这些优化机制显著提升了 synchronized 的性能,使其在高并发场景下仍然能够高效运行。

### Java 中 `synchronized` 关键字与 Monitor 机制的关系 Java 中的 `synchronized` 关键字是实现线程同步的重要机制之一,其底层依赖于 Monitor(监视器)模型来实现对共享资源的互斥访问和线程间的协调[^1]。Monitor 是一种同步构造,用于管理对共享资源的访问,确保同一时刻只有一个线程可以执行特定的代码块或方法。 在 Java 中,每个对象都与一个 Monitor 相关联,这个 Monitor 用于控制线程对对象的访问。当一个线程尝试进入由 `synchronized` 修饰的方法或代码块时,它必须先获取该对象的 Monitor 。如果该 Monitor 没有被其他线程持有,则当前线程获得并继续执行;如果 Monitor 已被其他线程持有,则当前线程将被阻塞,直到 Monitor 可用[^1]。 Monitor 机制的核心在于它提供了一种机制来确保线程的互斥执行,并支持线程之间的通信。Java 虚拟机通过对象的内部(Intrinsic Lock)和条件变量(Condition Variable)来实现 Monitor 的功能。每个对象都有一个与之关联的 Monitor,它包含一个入口集(Entry Set)和一个等待集(Wait Set)[^1]。 - **入口集**:当多个线程同时尝试获取对象的 Monitor 时,未能获取的线程会被放入入口集中等待。 - **等待集**:当线程调用 `wait()` 方法时,它会释放 Monitor 并进入等待集,直到另一个线程调用 `notify()` 或 `notifyAll()` 方法唤醒它。 以下是一个简单的代码示例,展示了如何使用 `synchronized` 关键字: ```java public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } ``` 在上述代码中,`increment()` 和 `getCount()` 方法都被 `synchronized` 修饰,这意味着每次只能有一个线程可以执行这些方法。当一个线程进入 `increment()` 方法时,它会获取 `Counter` 实例的 Monitor ,其他试图进入该方法的线程将被阻塞,直到第一个线程退出方法并释放[^1]。 Monitor 机制还支持线程间的协作,例如通过 `wait()`、`notify()` 和 `notifyAll()` 方法实现线程间的等待与唤醒。这些方法必须在 `synchronized` 上下文中调用,因为它们依赖于 Monitor 来确保线程安全。例如: ```java public class ProducerConsumer { private boolean available = false; public synchronized void produce() { while (available) { try { wait(); // 等待消费者消费 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } available = true; notifyAll(); // 唤醒所有等待的线程 } public synchronized void consume() { while (!available) { try { wait(); // 等待生产者生产 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } available = false; notifyAll(); // 唤醒所有等待的线程 } } ``` 在这个例子中,`produce()` 和 `consume()` 方法通过 `wait()` 和 `notifyAll()` 实现了线程间的协调。当生产者线程调用 `produce()` 方法时,如果资源已经存在,它会调用 `wait()` 进入等待状态;当消费者线程完成消费后,会调用 `notifyAll()` 唤醒所有等待的线程[^1]。 Monitor 机制在 Java 中的实现是通过 JVM 的字节码指令来完成的。具体来说,`monitorenter` 和 `monitorexit` 指令用于控制 Monitor 的获取和释放。每当一个线程进入由 `synchronized` 修饰的代码块时,JVM 会执行 `monitorenter` 指令来获取 Monitor ,而当线程退出代码块时,JVM 会执行 `monitorexit` 指令来释放[^1]。 ### Monitor 的实现细节 Monitor 的实现不仅涉及的获取和释放,还包括线程调度和状态管理。Java 虚拟机通过对象头(Object Header)中的 Mark Word 来存储的状态信息。Mark Word 中包含了对象的哈希码、GC 分代年龄、标志位等信息。当一个线程尝试获取时,JVM 会检查对象的 Mark Word 是否指向当前线程的栈帧中的记录。如果是,则说明当前线程已经持有该,可以直接执行;否则,线程需要竞争[^1]。 Monitor 机制还支持多种优化策略,如偏向(Biased Locking)、轻量级(Lightweight Locking)和重量级(Heavyweight Locking),这些优化策略旨在减少操作的开销,提高并发性能。例如,偏向允许某个线程在无竞争的情况下直接获取,而不需要进行同步操作;轻量级则通过 CAS(Compare and Swap)操作来实现的快速获取和释放;重量级则是传统的基于 Monitor 的实现,涉及线程的阻塞和唤醒,开销较大[^1]。 综上所述,`synchronized` 关键字通过 Monitor 机制实现了线程的互斥访问和线程间的协调。Monitor 提供了的获取与释放、线程的等待与唤醒等功能,确保了多线程环境下的数据一致性和线程安全。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

java干货仓库

觉得写的不错,就给博主投币吧

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

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

打赏作者

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

抵扣说明:

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

余额充值