ReentrantReadWriteLock源码分析

怎么加写锁的?

因为写锁和读锁、写锁都冲突,所以如果state非0,那么说明加锁失败,返回false,把当前线程加入CAS队列,有读锁释放的时候唤醒。

protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState(); //状态,高位包存了读锁的数量,低16位保存了写锁的数量
            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() || //监测是否需要等待,取值YES or NO,可以实现公平锁和非公平锁
                !compareAndSetState(c, c + acquires)) // CAS改变state
                return false;
            setExclusiveOwnerThread(current); // CAS成功,标志加锁成功
            return true;
        }

可以看到tryAcquire里面实现了读写锁冲突的语义,以及公平锁和非公平锁的语义。

怎么加读锁的?

同样读锁和写锁冲突,如果有线程持有写锁,那么加锁失败。否则判断是否有写锁在等待,没有就去CAS竞争,否则有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()&& //公平锁和非公平锁,公平锁:读锁和写锁之间有先后顺序,也就是有写锁在等待,那么读锁也需要排队。                  //非公平锁:可以自由竞争,但是为了防止写锁出现饥渴,会返回false,导致读锁等待(因为写少并且写重要些嘛)
                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 != current.getId())
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current); // 加锁失败,然后在for循环中反复尝试,直到成功或者遇到写锁
        }

另外一个疑问是如果当前线程持有写锁,另外一个线程在等待写锁,那么当前线程尝试加读锁是怎么样的?从上面代码可以看到这种情况在fullTryAcquireShared中处理:

final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                } else if (readerShouldBlock()) {
                          return -1;
                } 
                if (sharedCount(c) == MAX_COUNT) 
                    throw new Error("Maximum lock count exceeded"); 
                if (compareAndSetState(c, c + SHARED_UNIT)) { 
                    return 1; 
                } 
           } 
       }

仅保留了核心代码,可以看到在for循环中处理了两种情况:如果有写锁,那么是不是当前线程持有这个写锁,如果是那么CAS后加锁成功返回,不是就返回失败;如果没有写锁,那么判断是否有写锁在等待,如果有返回-1去CAS排队,如果没有,那么CAS竞争。加锁的代码已经分析完毕。

再来看释放锁的逻辑:

写锁:

 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;
        }
仍然是修改状态,因为是写锁,所以不需要CAS,直接修改状态即可,如果free是空,那么会打醒后面等待的线程,如果非空,说明当前线程是重入的。不需要唤醒后继线程。

读锁:

protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            // ...
            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;
            }
        }
只保留了核心代码,可以看到需要通过CAS来改变STATE的值,因为可以有多个线程同时持有读锁。重点看返回值,nextc如果为0,说明没有读锁,那么如果后继线程是writer的话,会唤醒来竞争锁。但是nextc如果非0,那么也应该唤醒后继的reader呀,其实唤醒reader的逻辑在acquire 读锁的时候就执行了,在tryAcquireshared中,如果加读锁成功,那么会返回1,这样就会唤醒等待的reader了,专门的术语叫Propagate(传播)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值