ReentrantLock 锁优化与 synchronized 锁膨胀的共同点

发布于:2025-03-24 ⋅ 阅读:(29) ⋅ 点赞:(0)

ReentrantLock 锁优化与 synchronized 锁膨胀的共同点

在 Java 并发编程中,ReentrantLocksynchronized 都是用于控制线程同步的锁机制。虽然它们在实现上有所不同,但它们在优化与锁膨胀的过程中具有一些共同点。本文将深入探讨它们的优化机制,并分析它们在高并发场景下的表现。

1. synchronized 锁膨胀机制

synchronized 在 Java 1.6 之后经历了多次优化,主要采用了 偏向锁 -> 轻量级锁 -> 自旋锁 -> 重量级锁 的升级策略。

1.1 锁的升级过程

  1. 偏向锁(Biased Locking)

    • 适用于单线程访问的情况。
    • 线程获取锁时,不会执行 CAS 操作,而是直接记录线程 ID。
    • 若没有竞争,则始终由同一线程持有。
  2. 轻量级锁(Lightweight Locking)

    • 适用于少量线程竞争的情况。
    • 线程尝试用 CAS 操作将锁对象的 Mark Word 更新为当前线程 ID。
    • 如果 CAS 失败,则表示有竞争,需要升级为自旋锁。
  3. 自旋锁(Spin Lock)

    • 适用于短时间竞争的情况。
    • 线程在获取锁失败时不会立即阻塞,而是执行若干次自旋(默认 10 次)。
    • 若在自旋期间仍然获取不到锁,则升级为重量级锁。
  4. 重量级锁(Heavyweight Locking)

    • 适用于高并发场景
    • 线程竞争激烈时,会触发锁的膨胀,导致线程进入阻塞状态,降低 CPU 空转。

1.2 synchronized 锁膨胀的触发条件

  • 线程竞争加剧:多个线程同时竞争锁,导致自旋失败。
  • 长时间自旋无效:自旋锁默认执行 10 次,若仍然无法获取锁,则进入重量级锁。
  • 对象被多个线程频繁修改:导致偏向锁撤销,退化为轻量级锁或更高级的锁。

2. ReentrantLock 锁优化机制

ReentrantLockjava.util.concurrent.locks 包提供的一种显式锁,它通过 AQS(AbstractQueuedSynchronizer)管理线程竞争。与 synchronized 类似,ReentrantLock 也有优化策略,但它提供了更精细的控制能力。

2.1 ReentrantLock 的优化策略

  1. 自旋 + CAS

    • 使用 CAS(Compare-And-Swap)操作尝试获取锁,避免线程立即进入阻塞。
    • 自旋失败后,进入等待队列,减少 CPU 资源浪费。
  2. 公平锁 & 非公平锁

    • 非公平锁(默认):线程尝试直接获取锁,提高吞吐量。
    • 公平锁:线程按照队列顺序依次获取锁,减少线程饥饿。
  3. AQS 队列优化

    • 线程在获取锁失败后,进入 AQS 的 FIFO 等待队列。
    • 通过 park() 挂起线程,避免频繁自旋消耗 CPU。
  4. 可中断锁

    • 线程在等待锁时,可以响应 interrupt() 进行中断,避免死锁问题。

3. 共同点分析

虽然 synchronized 和 ReentrantLock 采用不同的实现机制,但在锁优化过程中,它们具有以下共同点:

3.1 都采用自旋锁降低线程阻塞成本

  • synchronized 在轻量级锁阶段使用 CAS + 自旋,避免线程立即进入阻塞。
  • ReentrantLock 采用自旋 + CAS 进行锁竞争,提高性能。

3.2 都提供了从低开销到高开销的锁升级策略

  • synchronized 从 偏向锁 -> 轻量级锁 -> 自旋锁 -> 重量级锁 逐步升级。
  • ReentrantLock 通过 CAS 获取锁 -> AQS 队列,在竞争激烈时进入等待队列。

3.3 都利用 AQS 或队列管理线程竞争

  • synchronized 的重量级锁使用操作系统的 阻塞队列 来管理等待线程。
  • ReentrantLock 直接使用 AQS 维护一个 FIFO 队列。

3.4 都可以避免死锁和减少 CPU 资源浪费

  • synchronized 通过 自旋锁 + 线程阻塞 机制,降低 CPU 资源浪费。
  • ReentrantLock 允许可中断锁,避免线程无限等待,降低死锁风险。

3.5 都支持重入

  • synchronized 允许同一线程多次获取同一个锁
  • ReentrantLock 也支持可重入性,即同一线程可以多次 lock() 而不会阻塞自己。

4. 适用场景对比

特性 synchronized ReentrantLock
实现方式 JVM 内置锁,依赖对象头 Mark Word 依赖 AQS,基于 CAS 及队列实现
性能优化 偏向锁、轻量级锁、自旋、膨胀 自旋、AQS 队列、可中断锁
自旋锁 轻量级锁阶段使用 内部优化采用 CAS
锁升级机制 偏向锁 -> 轻量级锁 -> 重量级锁 CAS -> AQS 排队
可重入性 支持 支持
公平性 非公平,不能手动控制 支持公平锁与非公平锁
可中断 不支持 支持可中断锁
适用于 简单同步场景 复杂并发控制,细粒度锁

5. 总结

  1. synchronized 和 ReentrantLock 都采用了 CAS 和自旋锁优化锁竞争,以减少线程阻塞。
  2. 两者都提供锁升级机制,以适应不同的竞争强度,提高吞吐量。
  3. 两者都使用了队列管理线程等待,减少资源浪费,提升性能。
  4. ReentrantLock 提供了更丰富的功能,如公平锁、可中断锁等,适用于复杂并发场景,而 synchronized 适用于简单同步控制。

在选择时,

  • synchronized 适用于锁竞争较小、简单同步的场景
  • ReentrantLock 更适合高并发、大量竞争的复杂业务逻辑

希望这篇文章能帮助你更好地理解 synchronized 和 ReentrantLock 的优化机制及其共同点。🚀