Java并发--ReentrantReadWriteLock如何管理读写锁

前言

读写锁ReentrantReadWriteLock管理了一组锁,一个读锁,一个写锁。当写锁被获取到时,后续(非当前写操作线程)的读写操作都会被阻塞,写锁释放后,所有的操作继续执行。而当读锁被获取时,后续的任意线程的读锁都可被获取,写锁会被阻塞,当读锁被完全释放后,线程可以获取到写锁。可以看出读锁是共享式锁,写锁是独占式锁。那在ReentrantReadWriteLock中如何管理读写锁?读锁与写锁的获取释放过程是什么?

正文

ReentrantReadWriteLock中读写锁分别对应着ReadLock,WriteLock。ReadLock与WriteLock都有一个成员变量Sync。Sync是ReentrantReadWriteLock自定义的同步组件,继承了同步器AbstractQueuedSynchronizer。读写锁依赖自定义同步组件来实现同步功能,而读写状态就是其同步组件的同步状态。由于同步器AbstractQueuedSynchronizer的同步状态是整型变量state,如果利用一个整型变量维护多种状态,那就需要将该变量按位切割使用。读写锁将该变量分为两个部分,高16位表示读,低16位表示写。

在ReentrantReadWriteLock自定义的同步组件中有两个方法sharedCount与exclusiveCount,当传入的参数是当前同步状态时分别获取读锁的获取数量与写锁的获取数量。他们均采用位运算来实现。

static final int SHARED_SHIFT   = 16;

static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

sharedCount方法获取高位的值,即读锁被获取的数量。exclusiveCount方法获取低位的值,即写锁被获取的数量。当我们知道在ReentrantReadWriteLock中同步状态高低位代表不同含义,接下来看一下写锁的获取和释放。

写锁的获取和释放

写锁的获取

ReentrantReadWriteLock通过调用WriteLock的lock方法完成写锁的获取,lock方法调用同步器的acquire方法。

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

同步器的acquire方法会调用自定义同步组件的tryAcquire方法获取同步状态。

        protected final boolean tryAcquire(int acquires) {
            // 获取当前线程
            Thread current = Thread.currentThread();
            // 获取同步状态
            int c = getState();
            // 获取写锁的数量
            int w = exclusiveCount(c);
            // 判断是否已经获取到了锁
            if (c != 0) {
                // 没有获取写锁或者当前线程不是已持有写锁的线程
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                // 溢出
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 更新同步状态(重入性)
                setState(c + acquires);
                return true;
            }
            // CAS设置同步状态
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            // 设置持有锁的线程
            setExclusiveOwnerThread(current);
            return true;
        }

代码逻辑清晰明了,唯一需要注意的地方是writerShouldBlock方法,该方法判断该线程是否有资格获取锁,这与ReentrantReadWriteLock的公平与否有关。对于ReentrantReadWriteLock默认非公平性实现而言,任何线程都有资格获取写锁。但是如果ReentrantReadWriteLock被设置为锁公平竞争,writerShouldBlock需要判断当前线程是否为最优获取锁的线程。

公平性锁竞争

    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }

    public final boolean hasQueuedPredecessors() {
        // 同步队列尾节点
        Node t = tail; 
        // 同步队列头节点
        Node h = head;
        Node s;
        // 判断头节点的后继节点的线程是否为当前线程,即判断当前节点是否有前驱节点
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

写锁的释放

ReentrantReadWriteLock通过调用WriteLock的unlock方法完成写锁的释放。

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

unlock方法调用自定义同步组件的tryRelease方法释放同步状态。

        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;
        }

读锁的获取和释放

读锁的获取

ReentrantReadWriteLock会调用自定义同步组件的tryAcquireShared方法获取同步状态。

        protected final int tryAcquireShared(int unused) {
            // 获取当前线程
            Thread current = Thread.currentThread();
            // 获取同步状态
            int c = getState();
            // 如果写锁被另外一个线程获取,返回-1
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            // 读锁被获取的数量
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                     // 读锁未被任何线程获取
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    // 获取锁的线程是第一次获取读锁的线程
                    // 将线程(第一次获取读锁的)获取读锁的次数加1
                    firstReaderHoldCount++;
                } else {
                    // 获取锁的线程不是第一次获取读锁的线程
                    // 将线程(所有)获取读锁的次数的总和加1
                    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方法,判断该线程是否有资格获取锁,与ReentrantReadWriteLock的公平与否有关。与写锁的获取不同的是,非公平性的锁竞争并不是所有线程都可以有资格获取读锁。如果在同步队列中第一个阻塞的节点是独占式节点,即线程获取写锁,此时读锁没有资格获取锁。这是为了避免写锁饥饿,在使用ReentrantReadWriteLock的过程中,读的操作比写的操作多,如果不对其进行限制,线程将很难获取到写锁。

    final boolean readerShouldBlock() {
        return apparentlyFirstQueuedIsExclusive();
    }

    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            // 判断是否为独占式获取锁,即写锁
            !s.isShared()         &&
            s.thread != null;
    }

读锁的释放

ReentrantReadWriteLock会调用自定义同步组件的tryReleaseShared方法释放同步状态。

        static final int SHARED_SHIFT   = 16;
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);

        protected final boolean tryReleaseShared(int unused) {
            // 获取当前线程
            Thread current = Thread.currentThread();
            // 判断线程是否为第一次获取读锁的线程
            if (firstReader == current) {
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                // 将线程(所有)获取读锁的数量减1
                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();
                // 同步状态中读锁的数量减1
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值