Java并发编程-锁(七)

文章目录

  • AQS 的使用
    • 示例:读写锁
    • 拓展:锁的升级与降级
      • 一、内置锁 (`synchronized`) 的级别与升级
        • 1. 锁的级别
        • 2. 锁升级过程
      • 二、显式锁的降级(以 `ReentrantReadWriteLock` 为例)
        • 1. 什么是锁降级
        • 2. 锁降级的核心意义
        • 3. 经典实现示例
        • 4. 锁降级与升级的限制
      • 三、总结对比

AQS 的使用

示例:读写锁

刚刚提到的锁(比如Mutex和ReentrantLock)都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读 线程和其他写线程都被阻塞。

读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁能够简化读写交互场景的编程方式。

在没有读写锁支持的(Java 5之前)时候,如果需要完成这样的工作就要使用Java的等待通知 机制,就是当写操作开始时,所有晚于写操作的读操作都会进入等待状态,只有写操作完成并 进行通知之后,所有等待的读操作才能继续执行,而写操作之间依靠synchronized关键进行同 步,这样做的目的就是让读操作能读取到正确的数据,不会出现脏读。

改用读写锁实现上面的功 能,只需要在读操作时获取读锁,写操作时获取写锁就可以了。

当写锁被获取到时,后面的读写操作都会被阻塞,(但是当前写操作线程不会被阻塞,因为是可重入的)。

写锁释放之后,所有操作继续执行,这样的编程方式相对于使用 等待通知机制的实现方式来说,变得简单许多了是不是。

一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。在读多于写 的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。

Java并发包里提供读写锁的实现就是 ReentrantReadWriteLock,来看看它提供的特性。

Java并发编程-锁(七)_第1张图片

因为实现非常类似,所以这里就不再赘述它的实现细节。需要注意的一点是面试经常会问到的锁升级与锁降级,这里也简单梳理一下。

拓展:锁的升级与降级

在 Java 并发编程中,锁的级别与升级/降级机制是多线程优化的核心设计,主要分为 内置锁(synchronized)的升级和 显式锁(如读写锁)的降级,其核心目标是平衡性能与线程安全。

一、内置锁 (synchronized) 的级别与升级

1. 锁的级别

内置锁的级别以 锁状态 划分,包含以下 4 种状态:

锁状态 特点 底层实现
无锁 对象未被任何线程锁定
偏向锁 记录首次获取锁的线程ID,后续同一线程进入同步块无需CAS操作 对象头的 Mark Word 记录线程ID
轻量级锁 通过 CAS 自旋竞争锁,避免直接陷入线程阻塞 栈帧锁记录(Lock Record)
重量级锁 依赖操作系统互斥量(Mutex)实现,失败线程进入阻塞队列 操作系统调度
2. 锁升级过程

锁升级是 单向不可逆 的(从低开销到高开销),由 JVM 自动控制:

  1. 偏向锁 → 轻量级锁

    • 触发条件:其他线程尝试竞争偏向锁。

    • 步骤

      • 撤销偏向锁,暂停持有线程。

      • 检查线程状态:若存活,升级为轻量级锁;否则恢复无锁状态。

  2. 轻量级锁 → 重量级锁

    • 触发条件:CAS 自旋失败超过阈值(默认 10 次)。

    • 步骤

      • 锁膨胀为重量级锁,对象头指向互斥量。

      • 未获锁的线程进入阻塞状态。

优点:减少无竞争或低竞争时的锁开销,避免频繁内核态切换。

二、显式锁的降级(以 ReentrantReadWriteLock 为例)

1. 什么是锁降级

锁降级在保持高级别锁的同时获取低级别锁,随后释放高级别锁。例如:

  • 写锁 → 读锁:线程 A 持有写锁,先获取读锁后释放写锁。
2. 锁降级的核心意义
  • 保证数据可见性:写锁释放后,获取读锁可确保其他线程无法在此期间修改数据,仍能访问更新后的值。

  • 避免阻塞读操作:写锁降级为读锁后,其他读线程可以并发访问。

3. 经典实现示例
public void processData() {
    readLock.lock();
    if (!update) {
        readLock.unlock();
        writeLock.lock();
        try {
            if (!update) {
                // 更新数据
                update = true;
            }
            readLock.lock();
        } finally {
            writeLock.unlock(); // 写锁降级为读锁
        }
    }
    try {
        // 使用数据
    } finally {
        readLock.unlock();
    }
}

关键步骤

  1. 降级前:释放读锁,独占写锁完成数据修改。

  2. 降级时:获取读锁并释放写锁,确保后续数据使用的可见性。

  1. 降级后:其他读线程可获取读锁,写线程需等待当前读锁释放。
4. 锁降级与升级的限制
  • 不支持锁升级:读锁无法直接升级为写锁,因多线程持有读锁可能导致数据不一致。

三、总结对比

维度 锁升级 (synchronized) 锁降级 (ReadWriteLock)
触发方式 JVM 自动控制 手动编码控制
目的 减少无竞争时的开销,适应线程竞争变化 确保数据一致性,减少写锁独占时间
典型应用场景 synchronized 同步块或方法 读写锁(如数据更新后仍需线程安全读取)
可见性保证 默认由 JMM 内存模型保证 依赖显式锁获取顺序(写锁降级为读锁的代码逻辑)

你可能感兴趣的:(Java基础系列,java,开发语言)