ReentrantReadWriteLock

124 阅读5分钟

这个类是juc提供的读写锁,多个读操作可以同时加锁,写操作同时只能有一个线程加锁,也就是说读锁是共享的,写锁是互斥的,读锁和写锁之间也是互斥的。

AQS维护了state表示资源获取的情况,读写锁用这一个表示读锁和写锁的资源获取情况,高16位表示读锁的资源获取情况,低16位表示写锁的资源获取情况。

static final int SHARED_SHIFT   = 16;//读锁获取的共享资源是state右移16static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//写锁获取的排他资源是低16位static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

读锁

首先看一下读锁ReadLock的加锁方法,会调用到AQS的acquireShared方法

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

AQS的acquireShared方法,关键看tryAcquireShared怎么实现的

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

ReentrantReadWriteLock内部Sync实现了AQS,tryAcquireShared方法是Sync实现的,主要内容分为3步

  1. 判断当前是否被写锁锁定,如果是,判断写锁线程是否是当前线程,如果不是,返回-1,表示没有拿到资源,如果是,往下执行。
  2. 获取共享锁获取的资源数,如果满足readerShouldBlock是false、资源数小于最大资源数、CAS修改成功,则进入里面代码块。里面代码块首先判断r==0,说明当前没有别的读锁,则当前线程就是第一个读者线程;否则判断当前线程是不是第一个读者,如果是,就将重入数+1;否则,就获取当前线程的HoldCounter,如果cachedHoldCounter不是当前线程的HoldCounter,就从readHolds获取,readHolds是一个ThreadLocal对象,获取到holdCounter后,将当前线程获取的资源数+1。
  3. 如果第2步的条件判断不满足,则进入fullTryAcquireShared方法。

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 != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

fullTryAcquireShared方法第一步也是判断当前是否加了写锁,如果加了,是否是当前线程加的,如果不是,返回-1,没有获取到资源。

然后判断读者是否应该阻塞,如果应该阻塞,就判断当前线程是不是第一个读者,如果是,就说明当前读者已经获取到锁,是重入,允许向下执行;如果当前线程不是第一个读者,获取当前线程的holdCounter,如果等于0,说明当前线程还没有获取读锁,应该阻塞,返回-1,表示没有获取到资源。

然后判断读锁是否已经达到上限,如果是,抛出异常。

最后CAS修改资源,修改获取资源数。

final int fullTryAcquireShared(Thread current) {
    
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        //判断是否加了写锁        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        //如果读者需要阻塞,判断当前线程是否已经获取到锁,是重入
        } else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } 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)
                    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; // cache for release
            }
            return 1;
        }
    }
}


写锁

写锁WriteLock的锁方法会调用AQS的acquire方法。

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

AQS的acquire方法会尝试获取资源,关键就在尝试获取资源的tryAcquire方法里面。

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire里面有两个变量c和w,c表示当前资源数,w表示当前写资源数。

如果c不是0,表示当前有线程获取了资源,判断w是否等于0,如果w等于0,说明当前读锁获取了资源,写锁没有获取资源,获取资源失败;如果w不等于0,判断当前线程是否是获取写锁的线程,如果不是,获取资源失败。

如果可以获取到锁资源,判断锁资源是否达到最大值,如果达到,报错。

如果没达到,就增加资源数,返回true表示获取到了资源。

如果c等于0,判断写者是否需要等待,如果写者需要等待,或者CAS修改失败,返回false,表示没有获取到资源。

否则,设置获取资源的线程为当前线程,返回true,表示获取到了资源。

protected final boolean tryAcquire(int acquires) {
    
    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;}

公平和非公平

上面我们看到判断读者是否应该阻塞,写者是否应该阻塞,ReentrantReadWriteLock和ReentrantLock一样,都支持公平锁和非公平锁。读写锁的公平和非公平就是急于readerShouldBlock、writerShouldBlock来实现的。


公平,在公平FairSync里面,读者、写者判断是否需要阻塞都是判断当前线程是否有前面节点,如果有,就暂时不能获取资源,阻塞。

static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;
    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }
    final boolean readerShouldBlock() {
        return hasQueuedPredecessors();
    }
}

非公平,在非公平NonfairSync里面,写者直接返回false,表示写者不需要阻塞;读者判断同步队列的第一个等待的节点是否是写者线程,如果是,需要阻塞,如果不是,不需要阻塞。

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;
    final boolean writerShouldBlock() {
        return false; // writers can always barge
    }
    final boolean readerShouldBlock() {
       
        return apparentlyFirstQueuedIsExclusive();
    }
}



总结

同一个线程获取了写锁还可以获取读锁。