java读写锁ReentrantReadWriteLock源码分析

ReentrantReadWriteLock简介
①读写锁在同一时刻可允许多个读线程访问,但在写线程访问时,所有的读线程和其他写线程均被阻塞。保证了写操作对读操作的可见性
②读写锁维护了一对锁,一个读锁和一个写锁,分离读写锁提升并发性能
③一般情况下,读写锁比排它锁有更好的吞吐量和并发性

ReentrantReadWriteLock特性
公平性:支持公平和非公平获取锁方式,非公平吞吐量优于公平
重入:支持重入,读锁获取读锁后可以再次获取读锁,写锁获取写锁后可再次获取写锁,同时也可以获取读锁
降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁

ReentrantReadWriteLock结构

从上图可以看出:
1.读写锁ReentrantReadWriteLock内部维护了一个ReadLock读锁嵌套类和一个WriteLock写锁嵌套类。
2.ReadLock与WriteLock使用的时同一个Sync实例。Sync通过ReentrantReadWriteLock构造参数来控制使用公平、非公平模式。

1.Sync继承了AQS
2.公平模式FairSync和非公平模式NonfairSync继承自Sync

 读写锁实现类,从下图可以看出,ReadLock使用了共享模式,WriteLock使用了独占模式

AQS内部属性state:
对独占模式来说,通常0代表可获取锁,1 代表锁已被别人占用(重入例外),而共享模式下,每个线程都可以对state进行加减操作,也就是说,独占模式和共享模式对于state的操作完全不一样,那读写锁ReentrantReadWriteLock中state如何使用?

读写锁实现原理
读写锁同样依赖自定义同步器来实现同步功能,而读写状态就是其同步器的同步状态state,ReentrantLock中自定义同步器的实现,同步状态表示锁被一个线程重复获取的次数,而读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和一个写线程的状态。整型变量按位切割使用,高16位表示读,低16位表示写。

上图得出:当前同步状态表示一个线程已经获取了写锁且重入了两次,同时也连续获取了两次读锁。
假设 state = S
写状态 = S & 0x0000FFFF
读状态 = S >>> 16(无符号补0右移16位)
写状态 + 1 = S + 1
读状态 + 1 = S + (1 << 16) = S + 0x00010000
推论:S不等于0时,当写状态(S & 0x0000FFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取。

源码分析

abstract static class Sync extends AbstractQueuedSynchronizer {
    static final int SHARED_SHIFT   = 16;
    // 这个是读锁的原始累加值(也就是说每次获取读锁都是获取状态state,然后用state加它)是2^16
    // 举个例子,假设现在state为1,那么现在来获取读锁就是1+SHARED_UNIT
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    // 读锁和写锁的最大数量,都是2^16 - 1
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    // 写锁的掩码,其实就是2^16 - 1,这个数的二进制很特殊,16位全是1
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
    // 取c的高16位,代表读锁的获取次数(包括重入)
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    // 取c的低16位,代表写锁的重入次数,因为写锁是独占模式,只能由一个线程获取
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
    // 这个嵌套类的实例用来记录每个线程持有的读锁数量(读锁重入)
    static final class HoldCounter {
        // 持有的读锁数
        int count = 0;
        // 线程id
        final long tid = getThreadId(Thread.currentThread());
    }
    // ThreadLocal的子类
    static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }
    // 记录当前线程持有的读锁数量
    private transient ThreadLocalHoldCounter readHolds;

    // 用于缓存,避免从ThreadLocal的Map中查找,性能会好一些
    // 记录最后一个获取读锁的线程的重入次数
    private transient HoldCounter cachedHoldCounter;

    // 第一个获取读锁的线程(并且其未释放读锁)以及它持有的读锁数量
    // 这两个属性在读锁没有竞争情况下,很方便记录读锁重入次数
    private transient Thread firstReader = null;
    private transient int firstReaderHoldCount;

    Sync() {
        // 初始化readHolds这个ThreadLocal属性
        readHolds = new ThreadLocalHoldCounter();
        // 为了保证readHolds的内存可见性
        setState(getState());
    }
    ...
}

1.读锁获取

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

// AQS
public final void acquireShared(int arg) {
    // 调用Sync类的tryAcquireShared方法
    if (tryAcquireShared(arg) < 0) 
        doAcquireShared(arg);
}

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)) { // 读锁+1,因为是高16位,+SHARED_UNIT
           if (r == 0) {
              // r == 0说明此线程是第一个获取读锁,记录firstReader为当前线程及其持有的读锁数量1
              firstReader = current;
              firstReaderHoldCount = 1;
           } else if (firstReader == current) {
              // firstReader重入,简单+1即可
              firstReaderHoldCount++;
           } else {
              // 进入到这里,说明当前线程不是第一个获取读锁的,也不是第一个获取读锁线程的重入
              // cachedHoldCounter用于缓存最后一个获取读锁的线程(排除第一个,因为如果是第一个线程,使用了firstReader,那么它就不需要占用cachedHoldCounter)
              HoldCounter rh = cachedHoldCounter;
              // 如果缓存为空(不存在读锁竞争),如果缓存的不是当前线程则设置缓存为当前线程的HoldCounter。
              if (rh == null || rh.tid != getThreadId(current))
                  cachedHoldCounter = rh = readHolds.get();
              else if (rh.count == 0) 
                  // 缓存的是当前线程,但是count=0,把当前rh记录到ThreadLocal的map中
                  readHolds.set(rh);
              rh.count++;
           }
              return 1; // 读锁获取成功
           }
       return fullTryAcquireShared(current);
    }

上面分析了if分支,那么什么时候回进入到fullTryAcquireShared方法?(暂不考虑MAX_COUNT溢出情况)
第一种情况:readerShouldBlock返回true,分2种情况
FairSync.readerShouldBlock中执行hasQueuedPredecessors(),即阻塞队列中有其它元素在等待锁。也就是说公平模式下,有人在排队,你新来的不能直接获取锁。
NonFairSync.readerShouldBlock中执行apparentlyFirstQueuedIsExclusive(),即判断阻塞队列中head的第一个后继节点是否是来获取写锁的,如果是的话,让这个写锁先来,避免写锁饥饿。

final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    return (h = head) != null &&
        (s = h.next)  != null &&
        !s.isShared()         &&
        s.thread != null;
}

第二种情况:compareAndSetState(c, c + SHARED_UNIT) 这里CAS失败,说明存在竞争。可能是和另一个读锁获取竞争,当然也可能是和另一个写锁获取操作竞争。

// 因为CAS失败,如果就此返回,那么就要进入到阻塞队列了,但它满足了!readerShouldBlock(),也就是说本来可以不用到阻塞队列,所以该方法是增加cas成功获取读锁的概率
final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                // 如果由线程获取了写锁,并且不是当前线程,则获取读锁失败
                        return -1;
                } else if (readerShouldBlock()) {

                    // 进入到这里,说明了
                    // 1.exclusiveCount(c) == 0:写锁没有被占用
                    // 2.公平模式readerShouldBlock()为true,说明阻塞队列中有其他线程在等待,非公平模式readerShouldBlock()为true,说明head.next是来获取写锁的

                    // 既然是shuold block,为何会进入到这里?
                    // 为了处理读锁重入
                    if (firstReader == current) {
                        // firstReader线程重入读锁,则到达下面的cas
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                // 缓存为空或缓存的不是当前线程,则从当前线程ThreadLocal的Map中获取
                                rh = readHolds.get();
                                if (rh.count == 0) // 说明是readHolds.get()初始化的HoldCounter,读锁计数0,非重入,直接删除
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0) // 读锁计数0,非重入
                            return -1; // 获取读锁失败,进入到阻塞队列排队去
                    }
                }
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    // cas成功,表明获取了读锁
                    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;
                }
            }
        }

2.读锁释放

// ReadLock
public void unlock() {
    sync.releaseShared(1);
}

// Sync
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) {
        if (firstReaderHoldCount == 1)
            // 如果等于1,那么这次解锁后就不再持有锁了,把firstReader置为 null,给后来的线程用
            // 为什么不顺便设置firstReaderHoldCount = 0?因为没必要,其他线程使用的时候自己会设值
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        // 判断cachedHoldCounter是否缓存的是当前线程,不是的话要到ThreadLocal中取
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();

        int count = rh.count;
        if (count <= 1) {
            // 这一步将ThreadLocal remove掉,防止内存泄漏。因为已经不再持有读锁了
            readHolds.remove(); // 重复remove不会异常
            if (count <= 0)
                // 就是那种,lock()一次,unlock()好几次的逗比
                throw unmatchedUnlockException();
        }
        // count减1
        --rh.count;
    }
    for (;;) {
        int c = getState();
        // nextc是state高16位减1后的值
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // 如果nextc == 0,那就是state全部32位都为 0,也就是读锁和写锁都空了
            // 此时这里返回true的话,其实是帮助唤醒后继节点中的获取写锁的线程
            return nextc == 0;
    }
}

// 该方法分析见AQS源码分析三 https://blog.youkuaiyun.com/jiangtianjiao/article/details/103915459
private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                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;
        }
    }

读锁释放的过程还是比较简单的,主要就是将 hold count 减 1,如果减到 0 的话,还要将 ThreadLocal 中的 remove 掉。然后是在 for 循环中将 state 的高 16 位减 1,如果发现读锁和写锁都释放光了,那么唤醒后继的获取写锁的线程。

3.写锁获取
写锁是独占锁,如果有读锁被占用,写锁获取是要进入到阻塞队列中等待的。

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

// AQS
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        // 如果tryAcquire失败,那么进入到阻塞队列等待
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // 返回false存在的情况:
        // c != 0 && w == 0: 写锁可用,但是有线程持有读锁(也可能是自己持有)
        // c != 0 && w !=0 && current != getExclusiveOwnerThread(): 其它线程持有写锁
        // 也就是说只要有读锁或写锁被占用,这次就不能获取到写锁
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;

        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");

        // 这里不需要CAS,能到这里的只能是写锁重入,不然在上面的if处current != getExclusiveOwnerThread()就会被拦截
        setState(c + acquires);
        return true;
    }
    // 如果写锁获取不需要block,那么进行CAS,成功就代表获取到了写锁
    if (writerShouldBlock() || // 如果是非公平模式,writerShouldBlock()返回false,则直接cas抢锁,如果是公平模式,那么如果阻塞队列有线程等待的话,就乖乖去排队
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

4.写锁释放 

// WriteLock
public void unlock() {
    sync.release(1);
}

// AQS
public final boolean release(int arg) {
    // 1.释放锁
    if (tryRelease(arg)) {
        // 2.如果独占锁释放完全,唤醒后继节点
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// 释放锁过程是线程安全的,因为写锁是独占锁,具有排他性
// 实现很简单,state减1就是了
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);
    // 如果 exclusiveCount(nextc) == 0,也就是说包括重入的,所有的写锁都释放了,
    // 那么返回true,这样会进行唤醒后继节点的操作。
    return free;
}

参考:https://www.javadoop.com/post/reentrant-read-write-lock

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值