ReentrantReadWriteLock源码分析

本文深入剖析ReentrantReadWriteLock的内部实现,包括读写锁的state管理、共享锁和独占锁的获取与释放流程,以及AQS同步队列的交互机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在看本文前,如果对ReentrantLock不了解的,请先看 ReentrantLock源码分析,以免有些地方无法理解。
代码的执行说明,都已注释的方式写在了代码中,如果想看某个方法的分析,直接搜索方法名即可。本文中对于在ReentrantLock中详细说明过的方法以及相关分析在这里没有详细说明,请直接看ReentrantLock源码分析

ReadLock

lock
 public final void acquireShared(int arg) {
 	// 通关acquireShared尝试去获取共享锁
 	//获取成功了。就什么都不做。获取失败了,则执行doAcquireShared将当前线程加入到同步队列中
     if (tryAcquireShared(arg) < 0)
         doAcquireShared(arg);
 }

在看tryAcquireShared之前,需要先理解对于读写锁,是如何保存锁状态的?AQS是通过state来记录所得状态,而state只是一个值,对于读写锁来说,怎么用state来同时记录读和写两种锁的状态的呢?很容易想到的就是将state分为两部分,而ReentrantReadWriteLock也是这么做的,他使用低16位表示写锁状态,高16位用来记录读锁状态。具体如下:

 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;
 /** Returns the number of shared holds represented in count  */
 //取state的高16位,共享锁(读锁)
 static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
 /** Returns the number of exclusive holds represented in count  */
 //取state的低16位,独占锁(写锁)
 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
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();
    //获取独占锁,如果独占锁不为0,说明现在已经有线程获取了独占锁。再判断不是线程重入。
    //此时直接返回-1,表示获取锁失败了。因为独占锁与独占锁,独占锁与共享锁都是互斥关系。
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    //执行到这儿,说明独占锁状态为0.
    //获取共享锁的状态
    int r = sharedCount(c);
    //readerShouldBlock 判断的是读需不需要被阻塞
    //(实现后面会分析):在非公平锁中,实现判断就是说,当阻塞队列中的第一个等待线程为独占锁,返回true。
    //之所以这样判断,是因为,如果当前有线程持有共享锁,然后继续有持续不断的线程来获取共享锁,就会导致抢占独占锁的线程长时间无法获取锁而饥饿。
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        //重新设置state值来获取共享锁,可能有其他线程先修改了state,那这里就会失败,就会执行fullTryAcquireShared
        compareAndSetState(c, c + SHARED_UNIT)) {
        //执行到这儿,说明获取共享锁成功了。
        //这里判断r如果等于0说明当前线程是第一个获取共享锁的线程。
        if (r == 0) {
        	//firstReader用于保存第一个获得读锁的线程
        	//firstReaderHoldCount 用于保存第一个获取共享锁的线程的重入次数。
        	//这样做主要是为了当只有一个线程访问锁,没有竞争的时候,将锁的状态信息放在 firstReader和firstReaderHoldCount中,效率更高,因为后面你会发现要将当前线程的重入次数放在的ThreadLocal中。
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
        	//如果是第一个线程重入,则直接将重入次数++
            firstReaderHoldCount++;
        } else {
        	//cachedHoldCounter是一个HoldCounter,用来保存一个线程的重入次数的,这个变量正如其名,是一个缓存。
        	//因为要保存每一个线程的重入次数,这里将其保存在了ThreadLocal中,
        	//如果没有这个缓存,每次重入次数并修改,都要从ThreadLocal中获取,这里使用一层cache,主要就是为了提升性能,
        	//只有cache中的线程不是当前线程的时候,才会从ThreadLocal中获取。
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
            	//cache中的线程不是当前线程的时候,从ThreadLocal中获取。
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
          		//这里进行了set,这个set,有一种情况就是,一个线程释放了锁,释放的时候会将HoldCounter 从当前线程中remove,但是此时cachedHoldCounter还继续缓存着这个线程。
          		//如果刚释放又重新要获取锁,此时可能cachedHoldCounter又刚好是当前线程,那么这个时候ThreadLocal中已经没有数据了,
          		//此时就需要重新初始化ThreadLocal中的HoldCounter,重新设置值。
                readHolds.set(rh);
            //重入次数++,如果是第一次,这里++之后的结果就是1
            rh.count++;
        }
        return 1;
    }
    //如果不满足前面三个条件的任何一个条件,都会执行fullTryAcquireShared
    return fullTryAcquireShared(current);
}
//这里的逻辑其实就是获取锁,和tryAcquireShared中的很多逻辑是一样的,相同的地方,这里不再详细备注
//只是这里如果失败则通过死循环不断尝试知道满足条件返回。
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.
        } else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly
            //这里即使readerShouldBlock未true(说明等待队列中第一个节点是在抢占独占锁),但是可能是线程重入的情况
            //这里要判断,如果是重入则放过(重入次数++的逻辑,在后面做的),如果不是重入,则返回-1
            //判断不是重入的逻辑:首先线程不能等于firstReader ,其次其重入次数不等于0,因为等于0说明是第一次进入才初始化ThreadLocal中的HoldCounter。
           
           //这里判断等于firstReader ,说明是重入则放过
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
            	//拿到当前线程的HoldCounter
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        //如果重入次数为0,说明是第一次进入,因为要readerShouldBlock为true,所以要将其挂起,所以直接将其再删除掉。
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                //如果这是一个新的来竞争的线程,并且是第一次来获取锁,此时rh.count == 0,那就需要将当前线程挂起。
                //所以这里返回-1,说明获取锁失败了,要将其挂起。
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
        	//如果这里修改state的值成功了,说明获取共享锁成功,下面要做的就是通过ThreadLocak记录重入次数了。
        	//如果这里修改失败了,说明已经有其他线程修改了state,那就会进入下一次循环,再次来获取锁。
            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 (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                	//获取锁成功了,setHeadAndPropagate做了两件事。
                	//一是重新设置head
                	//二是唤醒下一个节点
                    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);
    }
}
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    //重新设置head
    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();
    }
}
unlock
public final boolean releaseShared(int arg) {
	//释放共享锁
    if (tryReleaseShared(arg)) {
    	//唤醒节点线程
        doReleaseShared();
        return true;
    }
    return false;
}
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;
    }
    for (;;) {
    	//修改锁标志state,因为共享锁释放存在多线程竞争的情况,所以这里使用CAS+循环直到释放成功
    	//只有state为0,才返回true说明已经全部释放。
        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;
    }
}
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;
                if (ws == Node.SIGNAL) {
                	//可能有多个线程同进行唤醒操作,这个CAS保证一个节点只会被unpark唤醒一次。
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                //执行到这儿,说明可能有其他线程已经执行了唤醒操作,ws已经被修改为0
                //在此通过CAS将0替换成PROPAGATE,此时如果替换成功,那说明确实已经被其他线程唤醒成功。则直接会执行h == head判断并跳出
                //如果CAS替换失败了,其他线程已经将其替换为了PROPAGATE。则下一次循环就行执行h == head判断并跳出
                //如果此时waitstatus不等于0,说明waitstatus等于PROPAGATE,说明第一个节点已经被其他线程唤醒,并且此时head还没有改变。
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            //判断head如果确实没有改变,则说明第一个节点已经被正常唤醒。
            if (h == head)                   // loop if head changed
                break;
        }
    }

WriteLock

lock
//WriteLock是独占锁,其获取锁的过程和ReentrantLock是相似的,区别在于tryAcquire的实现
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
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);
    //c != 0说明w肯定也是非0的。c == 0说明w == 0
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
       //有线程已经占用了独占锁,并且不是锁重入,直接返回false
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        //锁重入则修改state的值
        setState(c + acquires);
        return true;
    }
    //writerShouldBlock判断是否应该被阻塞
    //对于非公平锁,允许后来的线程抢占锁,就返回false
    //对于公平锁,如果等待队列有等待线程,则不允许插队,返回false
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}
unlock
//实现和ReentrantLock的unlock也是类似的,区别在于release的实现
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
//其实很简单,就是对state的值做减法操作,等于0则说明锁全部被释放。
protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值