高并发必备(八)!彻底掌握ReentrantReadWriteLock:从实战应用到AQS底层机制

目录

基本概念

读写锁的特点

与普通互斥锁的区别

核心结构

主要特性

1. 可重入性

2. 公平性选择

3. 锁降级

4. 不支持锁升级 

使用示例

基本用法

核心数据结构

状态变量(state)

AQS 核心概念

 AQS 核心思想

重要组件

AQS 核心源码解析 

AQS 共享模式实现

共享获取(acquireShared)

AQS 条件队列实现

条件等待(await)

AQS 实现类分析 

AQS 设计精髓

常见面试问题

读锁实现机制

读锁获取流程

写锁实现机制

写锁获取流程

公平性实现

公平模式

性能优化关键点

典型问题分析

为什么读锁不能升级为写锁?

为什么锁降级是安全的?

如何避免写线程饥饿?

总结


ReentrantReadWriteLock 是 Java 并发包中提供的一种读写锁实现,它允许多个线程同时读取共享资源,但在写入时只允许一个线程独占访问。

基本概念

读写锁的特点

  1. 读-读不互斥:多个线程可以同时获取读锁

  2. 读-写互斥:读锁和写锁不能同时持有

  3. 写-写互斥:同一时间只能有一个线程持有写锁

与普通互斥锁的区别

  • 普通互斥锁(如 ReentrantLock)在任何时候都只允许一个线程访问

  • 读写锁在读多写少的场景下能显著提高并发性能

核心结构

ReentrantReadWriteLock 由两部分组成:

  1. 读锁ReadLock

  2. 写锁WriteLock

ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

主要特性

1. 可重入性

与 ReentrantLock 类似,读写锁也是可重入的:

  • 线程可以重复获取已经持有的读锁

  • 线程可以重复获取已经持有的写锁

  • 持有写锁的线程可以获取读锁(锁降级)

  • 但持有读锁的线程不能直接获取写锁(可能导致死锁)

2. 公平性选择

构造函数可以指定公平策略:

// 非公平锁(默认)
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 公平锁
ReentrantReadWriteLock fairRwLock = new ReentrantReadWriteLock(true);

公平锁保证等待时间最长的线程优先获取锁,但会降低吞吐量。 

3. 锁降级

允许从写锁降级为读锁:

writeLock.lock();
try {
    // 写操作...
    readLock.lock();  // 锁降级
} finally {
    writeLock.unlock();
}
// 此时仍持有读锁
try {
    // 读操作...
} finally {
    readLock.unlock();
}

4. 不支持锁升级 

 不允许从读锁升级为写锁,尝试这样做会导致死锁:

readLock.lock();
try {
    writeLock.lock();  // 这里会阻塞,导致死锁
    try {
        // ...
    } finally {
        writeLock.unlock();
    }
} finally {
    readLock.unlock();
}

使用示例

基本用法

public class Cache {
    private final Map<String, Object> map = new HashMap<>();
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    
    public Object get(String key) {
        rwLock.readLock().lock();
        try {
            return map.get(key);
        } finally {
            rwLock.readLock().unlock();
        }
    }
    
    public void put(String key, Object value) {
        rwLock.writeLock().lock();
        try {
            map.put(key, value);
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

锁降级示例 

public void processData() {
    rwLock.readLock().lock();
    if (!cacheValid) {
        // 必须先释放读锁才能获取写锁
        rwLock.readLock().unlock();
        rwLock.writeLock().lock();
        try {
            // 再次检查,因为可能有其他线程已经修改了
            if (!cacheValid) {
                // 更新数据...
                cacheValid = true;
            }
            // 锁降级:在释放写锁前获取读锁
            rwLock.readLock().lock();
        } finally {
            rwLock.writeLock().unlock();
        }
    }
    
    try {
        // 使用数据...
    } finally {
        rwLock.readLock().unlock();
    }
}

核心数据结构

ReentrantReadWriteLock的底层实现依赖于AQS(AbstractQueuedSynchronizer),其核心数据结构包括:

状态变量(state)

// 高16位表示读锁数量,低16位表示写锁数量
volatile int state;

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT); // 读锁计数单位
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; // 最大读锁数65535
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 写锁掩码

锁持有者记录 

// 写锁持有者线程
private transient Thread owner;
// 读锁持有线程及计数
private transient ThreadLocalHoldCounter readHolds;
private transient HoldCounter cachedHoldCounter;

AQS 核心概念

AQS 是 Java 并发包中构建锁和同步器的核心框架ReentrantLockSemaphoreCountDownLatch 等都基于 AQS 实现。 

 AQS 核心思想

  • CLH 队列:基于双向链表的 FIFO 等待队列

  • 状态变量volatile int state 表示同步状态

  • 模板方法:提供 tryAcquire/tryRelease 等模板方法供子类实现

重要组件

// 同步队列头尾节点
private transient volatile Node head;
private transient volatile Node tail;

// 同步状态
private volatile int state;

// 节点状态
static final class Node {
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter; // 用于条件队列
}

AQS 核心源码解析 

获取资源(acquire) 

public final void acquire(int arg) {
    if (!tryAcquire(arg) && // 尝试获取资源
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列并等待
        selfInterrupt();
}

// 子类需要实现的方法
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

addWaiter 方法 

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node); // 队列为空时初始化
    return node;
}

acquireQueued 方法 

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { // 前驱是头节点且获取成功
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) && // 检查是否需要阻塞
                parkAndCheckInterrupt()) // 阻塞线程
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

释放资源(release) 

public final boolean release(int arg) {
    if (tryRelease(arg)) { // 子类实现释放逻辑
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h); // 唤醒后继节点
        return true;
    }
    return false;
}

 unparkSuccessor 方法 

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread); // 唤醒线程
}

AQS 共享模式实现

共享获取(acquireShared)

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r); // 设置头节点并传播
                    p.next = null;
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

共享释放(releaseShared) 

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;
                unparkSuccessor(h); // 唤醒后继节点
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;
        }
        if (h == head) // 如果头节点变化则继续
            break;
    }
}

AQS 条件队列实现

条件等待(await)

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter(); // 添加到条件队列
    int savedState = fullyRelease(node); // 完全释放锁
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) { // 不在同步队列中
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && // 重新获取锁
        interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters(); // 清理取消的节点
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

  条件通知(signal)

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

private void doSignal(Node first) {
    do {
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) && // 转移到同步队列
             (first = firstWaiter) != null);
}

AQS 实现类分析 

ReentrantLock 实现 

// 非公平锁实现
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

 CountDownLatch 实现

// 共享模式实现
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

protected boolean tryReleaseShared(int releases) {
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

AQS 设计精髓

  1. 模板方法模式:定义算法骨架,子类实现具体逻辑

  2. CLH队列变种:改进自旋锁为阻塞锁

  3. 状态管理state 变量实现灵活同步控制

  4. 公平性控制:通过队列顺序保证公平性

常见面试问题

  1. AQS 的底层数据结构是什么?

    • CLH 变种队列 + volatile state

  2. AQS 为什么使用双向链表?

    • 便于取消节点时快速找到前驱

  3. AQS 如何保证公平性?

    • 严格按照队列顺序获取锁

  4. AQS 的共享模式和独占模式区别?

    • 独占模式:同一时刻只有一个线程能获取

    • 共享模式:多个线程可以同时获取

  5. AQS 的条件队列和同步队列关系?

    条件队列节点被唤醒后会转移到同步队列

读锁实现机制

读锁获取流程

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    
    // 如果有写锁且不是当前线程持有的,获取失败
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
        return -1;
    
    int r = sharedCount(c);
    if (!readerShouldBlock() && 
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        // 读锁获取成功后的处理
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            // 更新线程本地读锁计数
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != current.getId())
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

读锁释放流程 

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    // 处理firstReader的情况
    if (firstReader == current) {
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        // 更新线程本地读锁计数
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != current.getId())
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    
    // CAS更新state
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

写锁实现机制

写锁获取流程

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    
    if (c != 0) {
        // 存在读锁或其他线程持有写锁
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 写锁重入
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    
    // 尝试获取写锁
    if ((w == 0 && writerShouldBlock()) ||
        !compareAndSetState(c, c + acquires))
        return false;
    
    setExclusiveOwnerThread(current);
    return true;
}

写锁释放流程 

protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

公平性实现

公平模式

// 公平锁的判断逻辑
final boolean writerShouldBlock() {
    return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
    return hasQueuedPredecessors();
}

 非公平模式

// 非公平锁的判断逻辑
final boolean writerShouldBlock() {
    return false; // 写锁可以直接尝试获取
}
final boolean readerShouldBlock() {
    // 防止写线程饥饿
    return apparentlyFirstQueuedIsExclusive();
}

性能优化关键点

  1. 读锁计数优化

    • 使用firstReaderfirstReaderHoldCount优化第一个读线程的计数

    • 使用cachedHoldCounter缓存最近一个读线程的计数

    • 使用ThreadLocal存储各线程的读锁计数

  2. 状态更新优化

    • 使用CAS操作更新state

    • 读锁释放采用自旋CAS

  3. 队列管理优化

    • 非公平模式下防止写线程饥饿

    • 公平模式下严格按队列顺序获取锁

典型问题分析

为什么读锁不能升级为写锁?

可能导致死锁:

线程A获取读锁
线程B尝试获取写锁(被阻塞)
线程A尝试升级为写锁(等待线程B释放读锁) → 死锁

为什么锁降级是安全的?

因为降级过程中写锁始终由当前线程持有,不会有其他线程能修改数据。

如何避免写线程饥饿?

非公平模式下通过apparentlyFirstQueuedIsExclusive()检查队列头部是否是写锁请求。

总结

ReentrantReadWriteLock的底层实现体现了以下设计思想:

  1. 状态压缩:32位int同时表示读锁和写锁状态

  2. 性能优化:读锁计数采用多种缓存策略

  3. 公平性控制:通过shouldBlock方法实现不同策略

  4. 安全性保证:严格的状态检查和CAS操作

理解这些底层机制,可以帮助我们更好地使用和优化读写锁,写出更高效的并发程序。

专栏合集在这里:

👉 《Java集合框架深度解析:从源码到实战应用》(已完结)
👉 《Java高并发实战:原理、源码与性能调优》(进行中)

📣 互动邀请
如果本文对您有帮助,请不要吝啬您的支持:

👍 点赞 - 您的点赞是我持续创作的最大动力!
🔔 关注 - 获取更多Java核心技术解析和实战技巧
💬 评论 - 有任何问题或建议欢迎留言讨论

🚀 下期预告
接下来我将继续深入讲解:

         《高并发必备(九)!读写锁的进化版?深入理解StampedLock的乐观读与锁升级机制》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值