ReentrantReadWriteLock源码分析

365 阅读4分钟

功能介绍

ReentrantReadWriteLock实现ReadWriteLock,支持与ReentrantLock同样的语义,但同时又比ReentrantLock性能更佳,针对读操作锁共享,针对写操作锁互斥,对于读操作远大于写操作的场景其性能更优。其也支持非公平锁、公平锁两种模式。

如何使用

class CacheData{
    Object data;
    volatile boolean cacheValid;
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
    void processCachedData() {
        rwl.readLock().lock();
        if (!cacheValid) {
            // 获取写锁之前必须释放读锁
            rwl.readLock.unlock();
            rwl.writeLock().lock();
            try{
                if (!cacheValid) {
                    data = ...
                    cacheValid = true;
                }
                // 释放写锁之前进行降级获取读锁
                rwl.readLock().lock();
            } finally {
                rwl.writeLock().unlock();
            }
        }
        
        try {
            use(data);
        } finally {
            rwl.readLock.unlock();
        }
    }
}

上面使用ReentrantReadWriteLock源码中示意代码展示了如何在更新缓存后执行锁降级(以非嵌套方式处理多个锁时,异常处理特别棘手)

class RWDictionary{
    private final Map<String, Data> m = new TreeMap<>();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();
    
    public Data get(String key) {
        r.lock();
        try { return m.get(key); }
        finally { r.unlock(); }
    }
    
    public String[] allKeys() {
        r.lock();
        try { return m.keySet().toArray(); }
        finally { r.unlock(); }
    }
    
    public Data put(String key, Data value) {
        w.lock();
        try { return m.put(key, value); }
        finally { w.unlock(); }
    }
    
    public void clear() {
        w.lock();
        try { m.clear(); }
        finally { w.unlock(); }
    }
}

在某些类型的集合的某些使用中,ReentrantReadWriteLock可用于提高并发性。只有当集合预期很大、被更多的读线程访问而不是写线程访问,并且需要的操作开销大于同步开销时,这种方法才有价值。例如,上述有一个使用TreeMap的类,该类预计是很大的,并且可以并发访问。

源码分析

下面我们根据源码来了解一下其如何控制读锁的共享以及写锁的互斥

我们先来了解下其整体的结构

private final ReentrantReadWriteLock.ReadLock readerLock;

private final ReentrantReadWriteLock.WriteLock writerLock;

final Sync sync;

public ReentrantReadWriteLock() {
    this(false);
}

public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

上述代码是ReentrantReadWriteLock的成员变量以及构造方法,可以看到当我们使用无参构造的时候默认使用的是NonfairSync同步器以及创建的ReadLock、WriteLock

下面我们来了解下它的Sync同步器如何控制state的状态来记录读锁以及写锁的,以及其如果定义读写锁的获取成功条件

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 6317671515068378041L;

    static final int SHARED_SHIFT   = 16;
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

    // 读锁数量 = state右移16位(即高16位)的值
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    // 写锁数量 = state低16位的值
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
    
    // 记录每个线程持有的读锁数量 
    static final class HoldCounter {
        int count = 0;
        final long tid = getThreadId(Thread.currentThread()); 
    } 
    
    // ThreadLocal 的子类 
    static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { 
        public HoldCounter initialValue() { 
            return new HoldCounter(); 
        } 
    } 
    
    /** 
    * 使用ThreadLocal来记录所有线程持有的读锁数量 
    */ 
    private transient ThreadLocalHoldCounter readHolds; 
    
    // 缓存最后一个读锁的线程
    private transient HoldCounter cachedHoldCounter; 
    
    // 第一个获取读锁的线程(并且其未释放读锁),以及它持有的读锁数量 
    private transient Thread firstReader = null; 
    private transient int firstReaderHoldCount; 
    
    Sync() {
        readHolds = new ThreadLocalHoldCounter(); 
        setState(getState()); 
    }

首先我们可以看到其对state{int}的值进行了重新定义,利用int类型4byte 32位的性质使用其高16位来记录共享锁的数量,低16位来记录独占锁的数量

2021-09-06_210311.png

NonfairSync与FairSync内置类

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;
    final boolean writerShouldBlock() {
        return false; // 写锁永不阻塞
    }
    final boolean readerShouldBlock() {
        // 如果head后的节点为写锁节点,则读锁获取应阻塞,避免写锁饥饿
        return apparentlyFirstQueuedIsExclusive();
    }
}
static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;
    final boolean writerShouldBlock() {
        // 如果队列中有节点,则阻塞
        return hasQueuedPredecessors();
    }
    final boolean readerShouldBlock() {
        // 如果队列中有节点,则阻塞
        return hasQueuedPredecessors();
    }
}

公平锁与非公平锁主要定义了读写线程的阻塞条件

独占锁的获取

    // 独占锁的获取
    protected final boolean tryAcquire(int acquires) {
        Thread current = Thread.currentThread();
        int c = getState();
        // 获取独占锁的数量w
        int w = exclusiveCount(c);
        if (c != 0) {
            if (w == 0 || current != getExclusiveOwnerThread())
                // 1.w == 0,存在共享锁,需等待共享锁释放后再进行获取独占锁,获取失败
                // 2.w != 0,存在独占锁,且当前线程不为独占线程,获取失败
                return false;
            if (w + exclusiveCount(acquires) > MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            // 当前线程重入,修改state值,获取独占锁成功
            setState(c + acquires);
            return true;
        }
        // c == 0,当前不存在读写锁
        if (writerShouldBlock() ||
            !compareAndSetState(c, c + acquires))
            // 公平锁及非公平锁的差异在此区分具体可看NonfairSync及FairSync对于writerShouldBlock方法的实现
            return false;
        // 设置独占线程为当前线程,获取锁成功
        setExclusiveOwnerThread(current);
        return true;
    }

2021-09-06_210356.png

独占锁的释放

    // 独占锁的释放
    protected final boolean tryRelease(int releases) {
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        int nextc = getState() - releases;
        // 判断释放后的独占锁占有数量是否为0
        boolean free = exclusiveCount(nextc) == 0;
        if (free)
            setExclusiveOwnerThread(null);
        setState(nextc);
        return free;
    }

2021-09-06_210407.png

共享锁的获取

    // 共享锁的获取
    protected final int tryAcquireShared(int unused) {
        Thread current = Thread.currentThread();
        int c = getState();
        // 独占锁数量不为0且当前线程不为独占线程,获取锁失败,锁降级基于此
        if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
            return -1;
        // 获取共享锁的数量r
        int r = sharedCount(c);
        if (!readerShouldBlock() &&
            r < MAX_COUNT &&
            compareAndSetState(c, c + SHARED_UNIT)) {
            // 读线程不被阻塞 && 读锁未超限制 && CAS修改state成功
            if (r == 0) {
                // 当前没有线程获取读锁
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                // 当前线程为第一个获取读锁的线程,此次重入
                firstReaderHoldCount++;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    // cachedHoldCounter为空 或者 当前线程不为cachedHoldCounter
                    // 从ThreadLocal中获取当前线程,赋值给cachedHoldCounter
                    cachedHoldCounter = rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                // 读锁数量 +1
                rh.count++;
            }
            return 1;
        }
        return fullTryAcquireShared(current);
    }
    
    // 读线程被阻塞 && 读锁超限制 && CAS修改state失败进入该方法
    final int fullTryAcquireShared(Thread current) { 
        HoldCounter rh = null; 
        for (;;) { 
            int c = getState(); 
            if (exclusiveCount(c) != 0) { 
                if (getExclusiveOwnerThread() != current) 
                    // 存在写锁,但不为当前线程,返回-1获取锁失败
                    return -1;
            } else if (readerShouldBlock()) {
                /**
                 * exclusiveCount(c) == 0 
                 * readerShouldBlock()返回true,表示阻塞队列中下一个节点为写锁的获取
                 * 为避免写锁饥饿,读锁的获取应避让
                 */
                if (firstReader == current) {
                    // firstReader为当前线程,锁重入,可避免避让
                } else {
                    if (rh == null) { 
                        rh = cachedHoldCounter; 
                        if (rh == null || rh.tid != getThreadId(current)) { 
                            rh = readHolds.get(); 
                            if (rh.count == 0) 
                                readHolds.remove(); 
                        } 
                    } 
                    if (rh.count == 0)
                        // 已获取读锁的线程没有当前线程,非重入,返回-1,获取锁失败
                        return -1; 
                } 
            } 
            // 进入这里代表是读锁重入,获取读锁
            if (sharedCount(c) == MAX_COUNT) 
                throw new Error("Maximum lock count exceeded"); 
            if (compareAndSetState(c, c + SHARED_UNIT)) { 
                if (sharedCount(c) == 0) { 
                    firstReader = current; 
                    firstReaderHoldCount = 1; 
                } else if (firstReader == current) { 
                    firstReaderHoldCount++; 
                } else { 
                    if (rh == null) 
                        rh = cachedHoldCounter; 
                    if (rh == null || rh.tid != getThreadId(current)) 
                        rh = readHolds.get(); 
                    else if (rh.count == 0) 
                        readHolds.set(rh); 
                    rh.count++; 
                    cachedHoldCounter = rh; 
                } 
                return 1; 
            } 
        } 
    }

2021-09-07_223806.png

共享锁的释放

    
    // 共享锁的释放
    protected final boolean tryReleaseShared(int unused) {
        Thread current = Thread.currentThread();
        if (firstReader == current) {
            // 第一个读锁线程 == 当前线程
            if (firstReaderHoldCount == 1)
                firstReader = null;
            else
                firstReaderHoldCount--;
        } else {
            // 第一个读锁线程 != 当前线程,获取最后一个获取读锁的线程
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                // 最后一个获取读锁的线程为null或者当前线程不为最后获取读锁的线程
                // 从ThreadLocal中获取当前线程的HoldCounter
                rh = readHolds.get();
            int count = rh.count;
            if (count <= 1) {
                // 从ThreadLocal中移除当前线程的HoldCounter
                readHolds.remove();
                if (count <= 0)
                    throw unmatchedUnlockException();
            }
            --rh.count;
        }
        for (;;) {
            int c = getState();
            int nextc = c - SHARED_UNIT;
            if (compareAndSetState(c, nextc))
                // 读锁的释放对读线程没有影响,但是如果state == 0就允许等待的写线程继续进程
                return nextc == 0;
        }
    }

2021-09-07_224941.png

思考为什么要有firstReader、firstReaderCount以及cacheHoldCounter

通过源码的阅读,我觉得应该是为了提升性能吧,读锁的获取及释放一般都是很快的,使用firstReader、firstReaderCount以及cacheHoldCounter来记录一些读线程的上锁记录,在释放时直接针对他们进行操作就不用去ThreadLocal中查找当前线程的锁记录了

ReadLock & WriteLock 内置类

public static class ReadLock implements Lock, java.io.Serializable {
    private final Sync sync;

    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    public void lock() {
        sync.acquireShared(1);
    }
    
    public void unlock() {
        sync.releaseShared(1);
    }
}
public static class WriteLock implements Lock, java.io.Serializable {
    private final Sync sync;

    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    public void lock() {
        sync.acquire(1);
    }

    public void unlock() {
        sync.release(1);
    }
}

了解上面Sync的源码,很容易理解ReadLock与WriteLock,主要就是使用Sync的独占锁以及共享锁获取方法来进行读写锁的获取,获取失败进入同步队列进行等待

总结

今天对ReentrantReadWriteLock的读写锁获取源码进行了学习,在我们了解的AQS的共享锁原理之后看这个就会事半功倍了

本文通过阅读源码以及自身的理解所写,其中若有不正之处烦请指正,感谢