ReentrantReadWriteLock 源码

120 阅读4分钟

我正在参加「掘金·启航计划」

ReentrantReadWriteLock 介绍

ReentrantReadWriteLock 读写锁主要使用在读多写少的场景中,读写锁允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞。

ReentrantReadWriteLock 还是基于 AQS 实现的。很多功能的实现和 ReentrantLock 类似,还是基于 AQSstate 来确定当前线程是否拿到锁资源。

ReentrantReadWriteLock 中,将 state 的高 16 位作为读锁的标识,而将低 16 位作为写锁的标识。

其中,读写锁有以下几种规则:

  1. 读读操作是共享的。

  2. 写写操作是互斥的。

  3. 读写操作是互斥的。

  4. 写读操作是互斥的。

  5. 单个线程获取写锁后,再次获取读锁,可以拿到。(写读可重入)

  6. 单个线程获取读锁后,再次获取写锁,拿不到,因为读锁是共享锁,对于其他线程来说是不安全的。(读写不可重入)

ReentrantReadWriteLock 写锁

写锁加锁操作

调用内部类 sync 中的 acquire() 方法。

public void lock() {
    // 调用内部类sync中的acquire()方法
    sync.acquire(1);
}

首先调用 tryAcquire() 方法,尝试获取锁资源,如果成功,则 acquire() 方法结束。如果获取失败,则执行 addWaiter() 方法和 acquireQueued() 方法。

public final void acquire(int arg) {
    // 首先调用tryAcquire()方法,尝试获取锁资源,如果成功,则acquire方法结束
    // 如果获取失败,则执行addWaiter()方法和acquireQueued()方法
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
    // 获取当前线程
    Thread current = Thread.currentThread();
    // 获取AQS中的state值
    int c = getState();
    // 这里将state和EXCLUSIVE_MASK进行与运算,获取写锁状态
    int w = exclusiveCount(c);
    // 如果c不等于0,说明有线程持有写锁或者读锁
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        // 如果w == 0是true,说明没有线程持有写锁,说明有线程持有读锁,读锁和写锁互斥,返回false
        // 如果w == 0是false,说明有线程持有写锁,写锁可重入,判断当前持有锁的线程是不是获取锁的线程,如果不是,返回false
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 程序走到这个if代码块,说明w != 0,持有锁的线程为当前线程
        // 这里查看更新state低位时是否超过最大值,超过则抛出异常
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        // 更新AQS的state值
        setState(c + acquires);
        return true;
    }
    // 如果c == 0,说明没有线程持有锁
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}
static final int SHARED_SHIFT   = 16;
// 1左移16为 00000000 00000001 00000000 00000000
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
// 00000000 00000000 11111111 11111111
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
// 00000000 00000000 11111111 11111111
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

exclusiveCount() 方法主要通过 运算获取写锁状态。

其中 addWaiter()acquireQueued() 方法和 ReentrentLock 中一样,这里就不重复分析了。

写锁释放锁操作

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    // 这里只有tryRelease方法是读写锁重新实现的,其他方法和ReentrantLock一样
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
    // 判断释放锁的线程是不是当前持有锁的线程
    if (!isHeldExclusively())
        // 如果不是,抛出异常
        throw new IllegalMonitorStateException();
    // state - 1
    int nextc = getState() - releases;
    // 获取state低16为状态
    boolean free = exclusiveCount(nextc) == 0;
    // 如果state低16位为0
    if (free)
        // 将持有互斥锁的线程信息置为null
        setExclusiveOwnerThread(null);
    // 设置state
    setState(nextc);
    return free;
}

ReentrantReadWriteLock 读锁

读锁加锁操作

public void lock() {
    sync.acquireShared(1);
}
public final void acquireShared(int arg) {
    // tryAcquireShared方法尝试获取锁资源,获取到返回1,没获取到返回-1
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
    /*
     * Walkthrough:
     * 1. If write lock held by another thread, fail.
     * 2. Otherwise, this thread is eligible for
     *    lock wrt state, so ask if it should block
     *    because of queue policy. If not, try
     *    to grant by CASing state and updating count.
     *    Note that step does not check for reentrant
     *    acquires, which is postponed to full version
     *    to avoid having to check hold count in
     *    the more typical non-reentrant case.
     * 3. If step 2 fails either because thread
     *    apparently not eligible or CAS fails or count
     *    saturated, chain to version with full retry loop.
     */
    // 获取当前线程
    Thread current = Thread.currentThread();
    // 获取state
    int c = getState();
    // 如果exclusiveCount(c) != 0是true,说明存在写锁,但是持有锁的线程不是当前线程没办法重入,所以获取锁资源失败返回-1
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    // 获取读锁信息
    int r = sharedCount(c);
    // 1. 如果是公平锁,有线程排队,返回true,直接跳过if代码块,有线程排队,返回false
    // 2. 如果是非公平锁:正常的逻辑是直接抢。
    // 因为是读锁,每次抢占只要CAS成功,必然成功。
    // 但是,这样就会出现问题,写操作将无法在读锁的情况抢占资源,导致写线程饥饿,造成一直阻塞。
    // 因此,非公平锁会查看next是否是写锁的,如果是,返回true,直接跳过if代码块,如果不是返回false
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        // 以CAS的方式对state的高16为加1
        compareAndSetState(c, c + SHARED_UNIT)) {
        // 这里表示已经成功获取锁资源
        // 如果读锁状态为0,说明当前没有线程持有读锁
        if (r == 0) {
            // 那么将firstReader设置为当前线程,表示第一个拿到锁资源的线程
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            // 如果firstReader是当前线程,说明是读锁重入,count加1,记录重入次数
            firstReaderHoldCount++;
        } else {
            // 如果不是第一个获取锁的线程
            HoldCounter rh = cachedHoldCounter;
            // 如果rh == null,说明当前线程是第二个拿到读锁的
            // 或者是之前有最后一个,但是不是当前线程
            // 将当前线程设置成最后一个
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            // 之前拿过,先在如果是0
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
static final class HoldCounter {
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    final long tid = getThreadId(Thread.currentThread());
}

/**
 * ThreadLocal subclass. Easiest to explicitly define for sake
 * of deserialization mechanics.
 */
static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

每个读操作的线程,在获取读锁时,都需要开辟一个 ThreadLocal。读写锁为了优化这个事情,做了两方面操作:

  1. 第一个拿到读锁的线程,不用 ThreadLocal 记录重入次数,在读写锁内有一个 firstReader 属性记录重入次数。
  2. 还记录了最后一个拿到读锁的线程的重入次数,交给 cachedHoldCounter 属性记录,可以避免频繁锁重入时,从 ThreadLocal 中获取。
final int fullTryAcquireShared(Thread current) {
    // 通过tryAcquireShared没拿到锁资源,也没返回-1,就走这
    /*
     * This code is in part redundant with that in
     * tryAcquireShared but is simpler overall by not
     * complicating tryAcquireShared with interactions between
     * retries and lazily reading hold counts.
     */
    HoldCounter rh = null;
    for (;;) {
        // 获取state
        int c = getState();
        // 如果有写锁但是不是当前线程持有,不可重入,直接返回-1
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {
            // 如果是公平锁,有排队,进入代码块,没有排队,跳过
            // 如果是非公平锁,判断head的next是写吗,如果是,进入代码块,如果不是,跳过
            // 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();
                    }
                }
                // 如果当前线程的次数是0,那么就不是重入操作
                if (rh.count == 0)
                    // 返回-1,阻塞等待
                    return -1;
            }
        }
        // 超过最大值,抛异常
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // CAS竞争锁资源
        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;
        }
    }
}

没获取到锁资源,执行下面方法,将当前线程加入队列,进行阻塞等待:

private void doAcquireShared(int arg) {
    // 将当前线程封装为Node,共享锁模式,并加入队列
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 获取当前线程Node的前一个节点
            final Node p = node.predecessor();
            // 如果前一个节点是head的话,再次尝试获取锁资源
            if (p == head) {
                int r = tryAcquireShared(arg);
                // 如果r >= 0,说明获取锁资源成功,设置Head,唤醒当前线程Node后面要获取读锁的线程
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}