AQS 源码解读系列--ReentrantReadWriteLock 篇

39 篇文章 0 订阅
21 篇文章 0 订阅

AQS 体系中针对不同的场景设计了不同的锁的实现,针对读多写少的场景,AQS 提供了 ReentrantReadWriteLock 读写锁来提高锁的效率,本篇我们一起走进它的实现原理。

1. readLock

1.1. lock()

public void lock() {
    sync.acquireShared(1);
}
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        // 尝试获取失败,自旋获取锁
        // 执行到这里,说明前面尝试获取锁的时候,其他线程持有了排他锁(写锁)
        // 或者当前线程是首次取获得读锁,但是队列中的第一个线程是等待读锁的线程
        // 因此 doAcquireShared 方法中通过 setHeadAndPropagate 方法传播
        doAcquireShared(arg);
}

tryAcquireShared()

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();
    // 读写锁的时候,int 型的 state 的高 16 位用来记录共享锁的获得次数,
    // 低16位用来记录排他锁的重入次数
    int c = getState();
    // 排他锁被被获取的次数不为0,且锁的 owner 不是当前线程,返回失败
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    // 这里之所以调用 readerShouldBlock() 方法判断头节点的后继节点是否在等待写锁,
    // 是为了防止写锁的饥饿,假如在写锁获取之前已经有线程获得了读锁,
    // 这里不进行判断就会导致后续获取读锁的线程会一直获得成功,从而使等待写锁的线程一直阻塞
    if (!readerShouldBlock() &&// 这个方法下面我们重点看
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {// 说明共享锁被首次获取
            firstReader = current;
            firstReaderHoldCount = 1;
        } 
        // firstReader 获得共享锁的次数会被缓存在 Sync 中
        // 如果当前线程是 firstReader,将缓存的数值自增
        else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
        	// 每个线程获得锁的数量也会被缓存在 Sync 中,每次获得锁成功要进行自增
            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()

final boolean readerShouldBlock() {
    /* As a heuristic to avoid indefinite writer starvation,
     * block if the thread that momentarily appears to be head
     * of queue, if one exists, is a waiting writer.  This is
     * only a probabilistic effect since a new reader will not
     * block if there is a waiting writer behind other enabled
     * readers that have not yet drained from the queue.
     */
    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) {
    /*
     * 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 (;;) {
        int c = getState();
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
            // 如果当前线程是锁的 owner,直接执行下面获取共享锁的逻辑
        } 
        // 如果排他锁被获取的次数为 0,先判断头节点的后继节点是否在等待排他锁,
        // 如果是的话,再判断获得锁的线程是否为当前线程
        else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                if (rh == null) {
                	// cachedHoldCounter 缓存的是最后一次成功获得共享锁的线程持有的锁的数量
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();// 当前线程的 HoldCounter
                        if (rh.count == 0)
                        	// 如果当前线程持有的共享锁的数量为 0,将当前线程对应的计数器移除
                            readHolds.remove();
                    }
                }
                // 如果当前线程持有的共享锁的数量为 0,返回失败
                if (rh.count == 0)
                    return -1;
            }
        }
        // 代码执行到这里,说明当前线程持有写锁或者
        // readerShouldBlock() 方法返回 false 或者
        // readerShouldBlock() 方法返回 true 且当前线程是第一个获得共享锁的线程或者
        // readerShouldBlock() 方法返回 true 且当前线程持有的共享锁的数量不为0
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 将共享锁的数量加1,同时更新缓存的信息以及计数器
        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;
        }
    }
}

doAcquireShared()

private void doAcquireShared(int arg) {
	// 加入到获取读锁的等待队列
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            // 前驱节点是头节点,再次尝试获取锁,此时一般不会获取失败
            if (p == head) {
                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) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

setHeadAndPropagate()

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
    /*
     * Try to signal next queued node if:
     *   Propagation was indicated by caller,
     *     or was recorded (as h.waitStatus either before
     *     or after setHead) by a previous operation
     *     (note: this uses sign-check of waitStatus because
     *      PROPAGATE status may transition to SIGNAL.)
     * and
     *   The next node is waiting in shared mode,
     *     or we don't know, because it appears null
     *
     * The conservatism in both of these checks may cause
     * unnecessary wake-ups, but only when there are multiple
     * racing acquires/releases, so most need signals now or soon
     * anyway.
     */
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        // 如果后继节点是在等待共享锁,将其唤醒
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

doReleaseShared()

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 至少经过两次循环,将 waitStatus 由 SIGNAL 更新成 PROPAGATE
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 当前节点唤醒后继节点后,后继节点发现自己的前驱节点为头节点,
        // 会去获取共享锁,获取成功后,将自己设置成新的头节点,
        // 因此当前线程执行到这里时,头节点很有可能已经发生变化,
        // 假如头节点已变,继续循环来唤醒新的头节点的后继节点,
        // 直至遇到等待读锁的节点或者到队尾
        if (h == head)                   // loop if head changed
            break;
    }
}

1.2. unlock()

public void unlock() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
    	// 当 state 中记录的共享锁的数量为 0 时,才会进入这里
    	// 这个方法在上面已经分析过,主要就是唤醒后继等待共享锁的节点
        doReleaseShared();
        return true;
    }
    return false;
}

tryReleaseShared()

protected final boolean tryReleaseShared(int unused) {
	// 更新线程计数器的值
    Thread current = Thread.currentThread();
    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;
    }
    // 更新 state 中共享锁的获取数量
    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;
    }
}

2. writeLock

2.1. lock()

public void lock() {
    sync.acquire(1);
}
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
    	// 尝试获得锁失败,将当前线程构造成 node 节点并加入队列自旋
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire()

protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. If read count nonzero or write count nonzero
     *    and owner is a different thread, fail.
     * 2. If count would saturate, fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
    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)
        // state 不为 0,如果写锁的数量为 0,说明读锁已被线程获取,此时获取失败
        // 如果写锁的数量不为 0,且当前线程不是锁的 owner,获取失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        // 执行到这里,写锁的数量不为 0 且当前线程是写锁的 owner,
        // 因此这里不需要 CAS,直接重入即可
        setState(c + acquires);
        return true;
    }
    // 执行到这里,说明 state == 0 成立,说明当前既无线程获得共享锁也无线程获得排他锁,
    // 但是有可能存在读锁的竞争,因此这里使用 CAS 尝试获得锁
    if (writerShouldBlock() ||     // 对于非公平锁而言,writerShouldBlock() 总是返回 false
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            // 前驱节点为头节点,再次尝试获取锁,
            // 获取成功后将当前节点设置成头节点,同时将旧的头节点从队列中移除
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

2.2. unlock()

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
        	// 唤醒后继节点
            unparkSuccessor(h);
        return true;
    }
    // 与共享锁的逻辑类似,当排他锁被完全释放时才返回 true,否则返回 false
    return false;
}
protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    // 锁被完全释放了,将 owner 置空
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

3. 总结

3.1. 工作流程描述

现在我们来结合具体的场景来分析一下读写锁的工作过程。

  1. 假如现在有一个线程 A 去获取读锁

    如果此时没有线程持有写锁(即 state 中记录的排他锁的数量为 0),则可以直接获取成功,获取成功后只需要将当前线程缓存的计数器及 state 中记录的总读锁的数量更新。

    如果此时有线程已经获得了写锁(即 state 中记录的排他锁的数量不为 0),且锁的 owner 不是 A 而是 B,则 A 会先尝试去获取锁,假如在尝试的时候 B 已经释放锁,A 可以获取成功,否则线程 A 会被构造成 node 节点加入等待队列,直到前驱节点将其唤醒,被唤醒后 A 会重新尝试获取读锁,获取成功后将自己设置成 head、更新计数并唤醒等待读锁的后继节点(在等待的过程中,可能会有其他尝试获取读锁的线程也加入了队列)。

    如果此时有线程已经获得了写锁(即 state 中记录的排他锁的数量不为 0),且锁的 owner 就是 A,则 A 只需要将当前线程缓存的计数器及 state 中记录的总读锁的数量更新。

    当 A 释放锁的时候,会更新当前线程缓存的计数器及 state 中记录的读锁的数量,并判断 state 中记录的总读锁的数量是否到达 0,即是否可以完全释放读锁,如果是则唤醒队列中的后继节点(如果存在)。

  2. 假如现在有一个线程A去获取写锁

    如果此时没有线程持有写锁或读锁(即 state 中记录的排他锁、共享锁的数量均为 0),则可以直接获取成功,获取成功后只需要将 state 中记录的总写锁的数量更新。

    如果此时有线程已经获得了读锁(即 state 中记录的读锁的数量不为 0),则 A 会被构造成 node 节点加入等待队列,直到前驱节点将其唤醒,被唤醒后 A 会重新尝试获取写锁,获取成功后将自己设置成头节点。

    如果此时有线程已经获得了写锁(即 state 中记录的排他锁的数量不为 0),且锁的 owner 不是 A,则 A 会被构造成 node 节点加入等待队列,直到前驱节点将其唤醒,被唤醒后 A 会重新尝试获取写锁,获取成功后将自己设置成头节点。

    如果此时有线程已经获得了写锁(即 state 中记录的排他锁的数量不为 0),且锁的 owner 就是 A,则可以直接获取成功(重入),获取成功后只需要将 state 中记录的总写锁的数量更新。

    当 A 释放锁的时候,会更新 state 中记录的读锁的数量,并判断 state 中记录的总写锁的数量是否到达 0,即是否可以完全释放写锁,如果是则唤醒队列中的后继节点(如果存在)。

3.2. 工作流程图解

  1. 初始状态下,多个线程可以同时获取读锁,此时队列为空(state 高16位记录读锁的数量,低16位记录写锁的数量)
    在这里插入图片描述
  2. 在读锁完全被释放之前,线程 B 尝试获取排他锁,最终会获取失败并加入队列
    在这里插入图片描述
  3. 在读锁完全被释放之前,线程 C 和 D 来尝试首次获取读锁(如果不是首次,可以获取成功),最终会获取失败也加入队列
    在这里插入图片描述
  4. 此时读锁被完全释放,最后一个释放读锁的线程会将头节点的后继节点即线程 B 代表的节点唤醒,线程 B 被唤醒后成功获得读锁,并将自己设置成头节点
    在这里插入图片描述
  5. 此时线程 B 释放写锁,释放锁后判断发现写锁被完全释放,便将头节点的后继节点即线程 C 代表的节点唤醒,线程 C 被唤醒后成功获得读锁,并将自己设置成头节点
    在这里插入图片描述
  6. 线程 C 将自己设置成头节点后,会唤醒后继的等待读锁的节点 D,线程 D 被唤醒后,将自己设置成头节点
    在这里插入图片描述
  7. 此时线程 C 和 D 都释放了读锁,最后一个释放读锁的线程会去执行唤醒后继节点的操作,这时不存在后继节点,因此队列保持
    在这里插入图片描述
  8. 此时进来的线程不管是获取读锁还是写锁,都会直接获取成功
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值