读写锁 ReentrantReadWriteLock 原理

产生背景

已经有了 ReentrantLock ,为什么还来一个 ReentrantReadWriteLock?

  • ReentrantLock 是独占锁,哪怕所有线程都是读操作,其实不用加锁,但是 ReentrantLock 一次还是只能让一个线程获取,ReentrantReadWriteLock 就是解决这个问题的
  • 简言之就是在读多的场景 ReentrantReadWriteLock 在读多的场景吞吐量比 ReentrantLock 好。技术是不断发展的,ReentrantReadWriteLock 也不完美,后面还有邮戳锁 StampedLock

读写锁定义:一个资源能同时被多个读线程访问,或者一个写线程访问,不能同时存在读写线程

读读共享

public class Test {

    public static void main(String[] args) {
        Resource resource = new Resource();
        
        new Thread(() -> {
            resource.read();
        }, "线程A").start();

        new Thread(() -> {
            resource.read();
        }, "线程B").start();
    }

}

class Resource{
    private final ReentrantReadWriteLock rrw =  new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rrw.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rrw.writeLock();

    public void read(){
        readLock.lock();
        System.out.println("获取读锁");
        try {
            System.out.println("读取数据....");
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            System.out.println("读取完成,释放读锁");
            readLock.unlock();
        }
    }
}

读写排他

public class Test {

    public static void main(String[] args) {
        Resource resource = new Resource();
        
        new Thread(() -> {
            resource.read();
        }, "线程A").start();
        
        // 休眠 100 毫秒
        try {
            TimeUnit.MILLISECONDS.sleep(100L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

		// 获取写锁,要等读锁释放才能获取
        new Thread(() -> {
            resource.write();
        }, "线程B").start();
    }

}

class Resource{
    private final ReentrantReadWriteLock rrw =  new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rrw.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rrw.writeLock();

    public void read(){
        readLock.lock();
        System.out.println("获取读锁");
        try {
            System.out.println("读取数据....");
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            System.out.println("读取完成,释放读锁");
            readLock.unlock();
        }
    }

    public void write(){
        writeLock.lock();
        System.out.println("获取写锁");
        try {
            System.out.println("写入数据....");
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            System.out.println("写入完成,释放写锁");
            writeLock.unlock();
        }
    }
}

锁降级

JDK ReentrandReadWriteLock 的一个示例

class CachedData {
    Object data;
    volatile boolean cacheValid; // volatile 变量,true:缓存可使用;false:缓存不可使用(需要初始化)
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    void processCachedData() {
        rwl.readLock().lock(); // 获取读锁
        if (!cacheValid) { // 如果缓存可使用,这个 if 不走,直接走下面的 user 方法,使用缓存。如果为 false,说明缓存不可用,要初始化它
            // Must release read lock before acquiring write lock
            rwl.readLock().unlock(); // 要先释放读锁,因为不能在读锁中获取写锁
            rwl.writeLock().lock(); // 获取写锁
            try {
                // Recheck state because another thread might have
                // acquired write lock and changed state before we did.
                if (!cacheValid) { // 再次判断缓存,双重检查的意味
                    data = ... // 维护缓存
                    cacheValid = true; // 维护后,缓存状态为可用
                }
                // Downgrade by acquiring read lock before releasing write lock
                rwl.readLock().lock(); // 获取读锁,当前是持有写锁的,写锁里可以获取读锁,这就是锁降级(但是不能反着来,也就是读锁里不能获取写锁)
            } finally {
                // 释放写锁
                rwl.writeLock().unlock(); // Unlock write, still hold read
            }
        }

        try {
            // 使用缓存
            use(data);
        } finally {
            // 释放读锁
            rwl.readLock().unlock();
        }
    }
}

原理

类结构

  • 类结构上和 ReentrantLock 类似,也是有三个内部类:Sync(AQS)、NonfairSync(非公平锁)、FairSync(公平锁)
  • 类结构上和 ReentrantLock 不同的是还有自己额外的两个内部类:WriteLock(写锁)、ReadLock(读锁)

ReentrantLock 底层依靠 AQS 实现,自己(子类 Sync )处理加锁释放锁,ReentrantReadWriteLock 也是一样的。区别也就是在这里

获取写锁

// java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock#lock
public void lock() {
    sync.acquire(1);
}

// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
    if (!tryAcquire(arg) && // 获取锁,自己实现
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // AQS 入队
        selfInterrupt();
}

// java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire
protected boolean tryAcquire(int arg) { // 模板方法
    throw new UnsupportedOperationException();
}

// java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryAcquire
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState(); // 资源状态(可能读也可能写)
    int w = exclusiveCount(c); // 写锁状态
    if (c != 0) { // 资源已经被持有(可能读也可嫩写)
        // 如果 w = 0,但是 c = 0,说明当前持有的是读锁,所以不允许获取写锁,但是如果是同一个线程可以重入
        if (w == 0 || current != getExclusiveOwnerThread())
            return false; // 获取锁失败,后面就要入队了,AQS 实现
        // 走到这里 w !=0 且 c!=0 且 就是同一个线程,重入,但是不能重入最大值(65535),不然报错
        if (w + exclusiveCount(acquires) > MAX_COUNT) // 
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires); // 修改 state
        return true;
    }
    // 走到这里是 c == 0 的场景,资源未被持有,可以入
    if (writerShouldBlock() || // 需要阻塞不,公平锁直接入队,如果是非公平锁要尝试插队
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

获取读锁

// java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock#lock
public void lock() {
    sync.acquireShared(1);
}

// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireShared
public final void acquireShared(int arg) {
    // tryAcquireShared 方法根据返回值获取锁(可能返回 -1,0,1,对于读写锁只返回 +1 和 -1)
    if (tryAcquireShared(arg) < 0) // -1 表示获取锁失败,+1 表示获取锁成功
        doAcquireShared(arg); // 如果获取锁失败走这里
}

// java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquireShared
protected int tryAcquireShared(int arg) { // 模板方法
    throw new UnsupportedOperationException();
}

// java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryAcquireShared
protected final int tryAcquireShared(int unused) {
    
    Thread current = Thread.currentThread();
    int c = getState(); // 资源状态
    if (exclusiveCount(c) != 0 && // 判断是否有线程持有写锁
        getExclusiveOwnerThread() != current) // 写锁线程是不是自己
        return -1; // 写锁已被别的线程持有,不能获取读锁,返回 -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 != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

// java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireShared
private void doAcquireShared(int arg) {
    // 注意是封装出来的节点类型是 Node.SHARED
    final Node node = addWaiter(Node.SHARED); // 和 ReentrantLock 一样,如果队列未初始化这里也要初始化(虚拟节点和第二个节点),如果队列已经有节点了,排在后面
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor(); // 当前节点上一个节点
            if (p == head) { // 如果是头节点,和 ReentrantLock 逻辑一样
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) && // 和 ReentrantLock 一样,是否需要阻塞(死循环,一定会返回 true)
                parkAndCheckInterrupt()) // park 线程
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
posted @   CyrusHuang  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示