并发编程源码解析(三)ReentrantReadWriteLock源码解析之一写锁

本文详细解析了ReentrantReadWriteLock中的读锁源码,包括acquireShared方法的工作原理,读写锁的区别,以及公平锁和非公平锁在readerShouldBlock方法中的应用,帮助理解并发编程中的锁机制和优化策略。

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

建议

阅读本篇前最好先阅读

并发编程源码解析(一)ReentrantLock源码解析(超详细)-优快云博客

并发编程源码解析(二)ReentrantReadWriteLock源码解析之一写锁-优快云博客

一、为什么要有读写锁?

        读取操作远远多于写入操作的场景,读取的操作没必要也锁定资源, 就使用读写锁可以让多个线程同时读取同一份数据共享锁资源,以提高并发效率。

二、读锁源码解析

2.1 锁

2.1.1 acquireShared 方法

因为其他方法没什么好介绍的咱直接介绍acquireShared方法。 

写锁因为共享,所以高16位只储存进入锁的线程数,如果说读锁重入那么重入次数是记录在ThreadLocal当中自行维护,详见readHolds.get() 方法。

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

        public final void acquireShared(int arg) {
            if (tryAcquireShared(arg) < 0)
                doAcquireShared(arg);
        }

        protected final int tryAcquireShared(int unused) {
            // 获取主线程 和 state 状态
            Thread current = Thread.currentThread();
            int c = getState();
            // c & EXCLUSIVE_MASK 方法,这样可以得到写锁的持锁状态
            // 回忆一下蒙板参数为 (1 << 16) - 1
            // 0000 0000 0000 0000 1111 1111 1111 1111
            // 如果存在写锁,写读可重入,所以如果是持有锁的线程是可重入,不用结束方法。
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            
            // return c >>> 16, 右移16位只剩下高位的16位,即只剩下读锁
            // 如: 0000 0000 0000 0001 0000 0000 0000 0001 右移后
            //     0000 0000 0000 0000 0000 0000 0000 0001
            int r = sharedCount(c);

            // readerShouldBlock 主要的功能是检查队列是否可以抢锁
            //这个非公平锁和公平锁逻辑不一致,在之后的文章再讲
            if (!readerShouldBlock() &&
                // 是否超出最大值
                r < MAX_COUNT &&
                // CAS尝试抢锁
                compareAndSetState(c, c + SHARED_UNIT)) {
                // 抢锁成功之后,如果是第一个进入则把 firstReader 参数赋予持锁线程
                // 并对firstReaderHoldCount复制参数
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    // 重入 firstReaderHoldCount++ 
                    firstReaderHoldCount++;
                } else {
                    // 不是第一个进入的会存入 cachedHoldCounter
                    // 如果cachedHoldCounter不存在 或者 cachedHoldCounter线程的Id与当前线程不同
                    // cachedHoldCounter 赋值为当前的线程
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    // 是原先的cachedHoldCounter 已经释放锁了
                    // 还没来得及改,于是重新把rh设置readHolds
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    // 写锁++
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

2.1.2 readerShouldBlock方法

公平锁

        //和ReentrantLock 逻辑一直
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }

非公平锁

        这段代码存在的是因为上一篇存在的读写不重入的原因导致的,如果读锁疯狂占用线程就有可能导致出现写饥饿的情况,写的线程会一直被锁住而得不到执行;这段代码的意义就是为了检查下一个线程是否是写锁,如果是写锁就将线程挂起,让写锁先执行来避免上述的情况。

        //非公平锁方法
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
        
        final boolean apparentlyFirstQueuedIsExclusive() {
            Node h, s;
            //  head 不为空
            return (h = head) != null &&
                // head 的下一个不为空
                (s = h.next)  != null &&
                //  且下一个锁不是被共享锁锁住
                !s.isShared()         &&
                // 且节点的线程不存在
                s.thread != null;
        }

2.1.3 readerShouldBlock方法 

  final int fullTryAcquireShared(Thread current) {
            
            HoldCounter rh = null;
            //尝试直到有一个结果为止
            for (;;) {
                int c = getState();
                //存在写锁挂起
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1; 
                } else if (readerShouldBlock()) {
                    if (firstReader == current) {
                        
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                //在这里清除为0 cachedHoldCounter
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        //没有抢到锁挂起
                        if (rh.count == 0)
                            return -1;
                    }
                }
                //判断是否超出最大重入次数
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 尝试抢锁
                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;
                }
            }
        }

2.1.4 doAcquireShared

    private void doAcquireShared(int arg) {
        // 添加进入双向链表,详情见ReentrantLock的篇中有详细介绍
        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;
                    }
                }
                // 负责轮询到前节点为 -1 的节点,详细讲解见ReentrantLock篇。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //unsafe.park() 方法挂起线程,顺便检查一下线程是否被中断。
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

2.2 释放锁

以下是释放锁的方法,我们讲对内部方法逐个解析

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

        public final boolean releaseShared(int arg) {
            if (tryReleaseShared(arg)) {
                doReleaseShared();
                return true;
            }
            return false;
        }

2.2.1 tryReleaseShared 方法

     protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            // 如果释放的是第一个线程,则正常的 -- 
            // 当释放最后一个的时候 firstReader 置为空 
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                //  如果不是第一个则同加锁逻辑相同,赋值 cachedHoldCounter 并 --
                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 (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                // CAS 的方式修改直到修改成功为止,如果为0则成功释放
                if (compareAndSetState(c, nextc))  
                    return nextc == 0;
            }
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值