ReentrantReadWriteLock源码分析

概述

ReentrantReadWriteLock是一个可重入的读写锁,写锁独占,读锁共享,支持公平和非公平两种方式加锁,源码可以看到,内部的Sync类重写了AQS中的tryAcquire,tryAcquireShared方法已经对应的释放方法。
独占式锁和共享式锁都已经分析很多了,今天我们简单看下ReentrantReadWriteLock的源码。测试例子依然可以在github中看到

ReentrantReadWriteLock 类结构

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    //读锁
    private final ReentrantReadWriteLock.ReadLock readerLock;
    //写锁
    private final ReentrantReadWriteLock.WriteLock writerLock;
    //AQS的子类
    final Sync sync;
    
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    ...
}

我们知道锁的实现都依赖于Sync这个AQS的子类,一起看看Sync的类结构吧

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

    //state的值 高16位表示读锁(或者共享锁)的次数, 低16位表示写锁(独占锁)的次数(注意重入也会累加)
    static final int SHARED_SHIFT   = 16;
    //所以读锁加锁成功一次会加一个SHARED_UNIT
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    //最大加锁量
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    //写锁计算的因子,高16位全是0,低16位全是1,state值相与就是写锁量
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

    //读锁量或者叫共享量  因为是高16位表示,计算具体值是左移16位
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    //计算写锁量 将state和EXCLUSIVE_MASK相与计算写锁量
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

    //线程占用的读锁量(重入性)
    static final class HoldCounter {
        int count = 0;
        // Use id, not reference, to avoid garbage retention
        final long tid = getThreadId(Thread.currentThread());
    }

    static final class ThreadLocalHoldCounter
        extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }
    
    private transient ThreadLocalHoldCounter readHolds;

    //记录最新获取读锁线程的缓存计数
    private transient HoldCounter cachedHoldCounter;

    //第一个获取读锁的线程
    private transient Thread firstReader = null;
    //第一个获取读锁线程的计数
    private transient int firstReaderHoldCount;

    Sync() {
        readHolds = new ThreadLocalHoldCounter();
        setState(getState()); // ensures visibility of readHolds
    }
    。。。
}

我们以非公平的读锁加锁过程举例:\

public void lock() {
    sync.acquireShared(1);
}
protected final int tryAcquireShared(int unused) {
    /*
     * 1.如果被其他线程加了写锁,直接返回失败
     * 2. 否则线程不应该阻塞,没达到最大值,进行CAS增加State数量
     *      成功则加锁成功,如果是第一线程加锁赋值firstReader ,
     *      如果不是第一个线程,判断最新的读锁线程是不是自己,不是则将自己赋给最新线程,
     *      增加HoldCounter计数(本线程的读锁量)
     * 3. 如果第二步失败,走完整的CAS替换流程
     */
    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 != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

readerShouldBlock是一个抽象方法,公平子类和非公平子类复写该方法,我们看下非公平子类的readerShouldBlock方法逻辑

final boolean readerShouldBlock() {
    return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    //如果队列中第一个是写等待,则返回true
    //防止写等待一直被阻塞着,毕竟读是共享的,人多抢的多
    return (h = head) != null &&
        (s = h.next)  != null &&
        !s.isShared()         &&
        s.thread != null;
}

上面我们看到如果第二步没有抢到读锁则进入充分请求锁阶段fullTryAcquireShared

final int fullTryAcquireShared(Thread current) {
    
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        //如果其他线程占了写锁,返回加锁失败
        if ((c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
        } else if (readerShouldBlock()) {
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                //不是重入的,则返回-1
                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)
                    return -1;
            }
        }
        //到了这里就是应该加读锁的
        if (sharedCount(c) == MAX_COUNT) //读锁量最大后抛出异常
            throw new Error("Maximum lock count exceeded");
        //进行CAS替换,替换成功则加锁成功,进行必要的属性赋值就好了(和上面的快速加锁逻辑一致)
        //替换失败,其他线程读锁加成功了,for循环再来一次
        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; // cache for release
            }
            return 1;
        }
    }
}

上面就是加非公平读锁的逻辑了,要先看AQS源码篇哦。
公平读锁的逻辑只有readerShouldBlock方法不一样,公平读锁的readerShouldBlock方法,只要该线程不是队列的第二节点,就返回true,这里就不详细看了。
读锁的释放

public void unlock() {
    sync.releaseShared(1);
}
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    //进行本线程的读锁量操作,如果是firstReader操作firstReader,否则得到HoldCounter操作
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    //CAS操作state, state减为0,读锁完全释放,唤起后继节点
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            return nextc == 0;
    }
}

下面我们看看写锁的加锁过程

public void lock() {
    sync.acquire(1);
}
protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. state不为0,说明被加了读锁或写锁,判断写锁为0或者写锁不是自己,则返回失败.
     * 2. 如果写锁满了返回失败,如果是自己重入的写锁,直接修改state的值,返回加锁成功
     * 3. 如果写锁应该阻塞或者CAS设置state失败(说明加锁失败),返回false.
     *    CAS 设置成功,设置独占线程为自己,返回加锁成功
     */
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

非公平加写锁,writerShouldBlock总是返回false

final boolean writerShouldBlock() {
    return false; // writers can always barge
}

公平加写锁也是只有writerShouldBlock方法不同,公平加写锁writerShouldBlock方法需要判断是否是第二节点。这里就不详细介绍了。
写锁的释放

public void unlock() {
    sync.release(1);
}
protected final boolean tryRelease(int releases) {
    //判断是否是独占线程
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //state减去释放量,如果减为0,说明全部写锁释放,返回true,进行后继节点的唤醒。
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。