Bootstrap

synchronized 锁的性能优化:自适应自旋、锁消除、锁粗化与偏向锁

在并发编程中,锁的机制至关重要。synchronized 作为 Java 中最基础的同步工具,长期以来被广泛使用。然而,synchronized 的性能问题也一直存在,尤其是在多线程高并发的场景下,频繁的加锁和解锁操作会带来不小的开销。为了解决这一问题,JDK 1.6 引入了多种优化技术,以提高 synchronized 锁的性能,降低系统开销。

本文将详细介绍 JDK 1.6 中对 synchronized 锁的优化,包括 自适应自旋锁锁消除锁粗化 以及 偏向锁、轻量级锁、重量级锁,帮助你理解这些技术背后的原理,并在实际开发中充分利用它们。

1. 自适应自旋锁:减少不必要的等待

自旋锁的概念

自旋锁是指线程在获取锁失败时,不进入阻塞状态,而是不断循环尝试获取锁。自旋的缺点在于如果自旋时间过长,可能会浪费大量 CPU 时间,导致性能下降。

自适应自旋锁优化

在 JDK 1.6 中,自适应自旋锁被引入来解决自旋时间过长的问题。自适应自旋锁会根据系统的负载、锁的竞争情况等多种因素动态调整自旋的次数和时间,避免了传统自旋锁的资源浪费。

  • 如果线程获得锁的成功率高,那么自旋时间短,能够尽快获得锁。
  • 如果线程获得锁的成功率低,自旋时间则较长,避免了长时间自旋带来的性能损耗。

通过自适应的自旋策略,JDK 1.6 使得自旋锁变得更聪明、更高效。

2. 锁消除:消除不必要的锁

锁消除的概念

锁消除是指在某些情况下,JVM 会分析并移除不必要的同步操作。例如,当 JVM 发现某些对象永远不会被多线程同时访问时,就可以消除对这些对象的锁,从而提高性能。

应用示例

例如,StringBuffer 类的 append 方法可能被 synchronized 修饰,但在某些情况下,StringBuffer 的对象仅在一个线程中使用。在这种情况下,JVM 可以消除无意义的 synchronized 锁,避免不必要的性能开销。

@Override
public synchronized StringBuffer append(Object obj) {
    toStringCache = null;
    super.append(String.valueOf(obj));
    return this;
}

通过 逃逸分析,如果 JVM 确定 StringBuffer 对象仅会在单线程中使用,它可以消除 synchronized 锁,因为单线程中的操作自然是线程安全的。

3. 锁粗化:减少无谓的加锁解锁

锁粗化的概念

锁粗化是指将多次加锁解锁的操作合并为一次大的加锁解锁操作。例如,如果一个对象在多个地方多次加锁,我们可以将这些锁合并为一个大范围的同步块,从而减少锁的申请与释放次数,提升性能。

应用示例

假设我们有如下代码:

public void lockCoarsening() {
    synchronized (this) {
        // do something
    }
    synchronized (this) {
        // do something else
    }
    synchronized (this) {
        // do another thing
    }
}

这段代码中,synchronized 被多次调用,但实际上每次加锁解锁的内容都非常短。如果将它们合并成一个大的同步块:

public void lockCoarsening() {
    synchronized (this) {
        // do something
        // do something else
        // do another thing
    }
}

通过锁粗化,减少了锁的申请和释放次数,显著提升了性能。锁粗化在没有竞争的情况下尤其有效,但在循环中不适用,因为可能导致线程长时间无法获得锁。

4. 偏向锁、轻量级锁、重量级锁:锁的多级演进

在 JDK 1.6 中,synchronized 锁的实现被优化为偏向锁、轻量级锁和重量级锁,这三种锁分别适用于不同的竞争情况。

4.1 偏向锁

偏向锁的思想是,如果某个锁长期没有竞争,那么该锁就不需要加锁,只需要打一个标记即可。当第一个线程获得锁时,锁会记录下该线程的信息,并允许后续的线程直接获取锁(如果是同一个线程)。

  • 性能最优:避免了加锁操作,极大地减少了同步开销。
  • 适用场景:适合没有线程竞争的情况。

4.2 轻量级锁

当有多个线程竞争一个偏向锁时,偏向锁会升级为轻量级锁。轻量级锁使用 CAS(比较和交换)操作来尝试获取锁,避免线程阻塞。

  • 性能较好:通过自旋和 CAS 操作避免了线程阻塞。
  • 适用场景:适合竞争较少、锁持有时间较短的场景。

4.3 重量级锁

当锁的竞争较为激烈,并且自旋和 CAS 无法有效解决时,轻量级锁会升级为重量级锁。重量级锁依赖操作系统的同步机制,涉及线程的阻塞和唤醒,因此开销较大。

  • 性能最差:涉及线程阻塞和上下文切换,性能开销较大。
  • 适用场景:适合长时间锁竞争的情况。

锁的升级路径

  • 无锁偏向锁轻量级锁重量级锁

5. 小结

JDK 1.6 在 synchronized 锁的基础上引入了多项优化,包括自适应自旋锁、锁消除、锁粗化和偏向锁、轻量级锁、重量级锁等。这些优化显著提高了并发程序的性能,特别是在多线程高并发的环境下。具体来说:

  • 自适应自旋锁:根据实际竞争情况动态调整自旋时间,避免不必要的性能开销。
  • 锁消除:通过逃逸分析消除不必要的锁,提高效率。
  • 锁粗化:减少无意义的锁申请和释放,提升执行效率。
  • 偏向锁、轻量级锁、重量级锁:通过不同的锁状态来适应不同的线程竞争情况,提高整体性能。

了解这些优化技术并合理运用,可以帮助开发者编写更加高效的并发程序,充分发挥 Java 并发性能的潜力。

🌟 关注我的CSDN博客,收获更多技术干货! 🌟

;