JUC源码解析八:ReentrantReadWriterLock读写锁源码解析

一 前言

ReentrantReadWriterLock这个类顾名思义就是一个同时支持读锁与写锁的可重入类。跟ReentrantLock相比不仅多了读锁的可共享锁,还对读与写进行了分类,在内部对其进行协调(读锁升级为写锁、写锁降级为读锁)。另外读写锁最多支持2^16-1个递归写入锁和2^16-1个递归读取锁,相比ReentrantLock2^32-1少了很多,原因则是ReentrantReadWriterLock使用了state的高位16位表示共享锁的数量,低位16位表示独占锁可重入的数量。

写锁是独占锁,也叫排它锁,当某一线程使用了写锁时,其他锁(包括共享锁)不能够操作被锁住的资源。而读锁则可以由多个读锁共享资源。两种所不能够同时使用。总的来说则是:一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。也就是说读写锁使用的场合是一个共享资源被大量读取操作,而只有少量的写操作(修改数据)

写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级的特性而读取锁是不能直接升级为写入锁的。因为获取一个写入锁需要释放所有读取锁,释放后获得写入锁即实现了锁升级的特性。

由于这里实现的内部类较多,先放一个表格:

作用
Sync,继承AQS,锁功能的主要实现者
FairSync继承Sync,主要实现公平锁
NofairSync继承Sync,主要实现非公平锁
ReadLock读锁,通过sync代理实现锁功能
WriteLock写锁,通过sync代理实现锁功能

一些基础的代码:

	/** 内部类  读锁 */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** 内部类  写锁 */
    private final ReentrantReadWriteLock.WriteLock writerLock;

    final Sync sync;

    /** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */
    public ReentrantReadWriteLock() {
        this(false);
    }

    /** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    /** 返回用于写入操作的锁 */
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    /** 返回用于读取操作的锁 */
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

    abstract static class Sync extends AbstractQueuedSynchronizer {
        /**
         * 省略其余源代码
         */
    }
    public static class WriteLock implements Lock, java.io.Serializable{
        /**
         * 省略其余源代码
         */
    }

    public static class ReadLock implements Lock, java.io.Serializable {
        /**
         * 省略其余源代码
         */
    }

可以看出ReentrantReadWriteLockReentrantLock一样,其锁主体依然是Sync,它的读锁、写锁都是依靠Sync来实现的。所以ReentrantReadWriteLock实际上只有一个锁,只是在获取读取锁和写入锁的方式上不一样而已,它的读写锁其实就是两个内部类:ReadLockwriteLock,这两个类都是lock实现。

在里面读写锁通过位运算迅速确定读锁和写锁的状态。代码如下:

		// 以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;

		// 将状态位c右移16位得到共享锁的状态位
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
		// 将状态位c与低十六位1取与得到独占锁的状态位
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

ReadLock读锁的实现:

	public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
        private final Sync sync;

        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

        // 争夺读锁
        public void lock() {
            sync.acquireShared(1);
        }

        // 以共享锁的模式竞争锁,线程被中断时抛出异常
        public void lockInterruptibly() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
        }

        // 尝试争夺锁
        public boolean tryLock() {
            return sync.tryReadLock();
        }

        // 尝试在有限时间内争夺锁
        public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
        }

        // 尝试释放此锁定
        public void unlock() {
            sync.releaseShared(1);
        }

        // ReadLocks是共享锁,不需要支持Condition条件队列,因此调用会直接抛出异常
        public Condition newCondition() {
            throw new UnsupportedOperationException();
        }

        public String toString() {
            int r = sync.getReadLockCount();
            return super.toString() +
                "[Read locks = " + r + "]";
        }
    }

WriteLock写锁的实现:

	public static class WriteLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -4992448646407690164L;
        private final Sync sync;

        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

        // 争夺读锁
        public void lock() {
            sync.acquire(1);
        }

        // 竞争锁,线程被中断时抛出异常
        public void lockInterruptibly() throws InterruptedException {
            sync.acquireInterruptibly(1);
        }

        // 尝试获取写锁
        public boolean tryLock( ) {
            return sync.tryWriteLock();
        }

        // 在规定时间内获取锁
        public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireNanos(1, unit.toNanos(timeout));
        }

        // 释放锁
        public void unlock() {
            sync.release(1);
        }

        // 得到等待队列Condition
        public Condition newCondition() {
            return sync.newCondition();
        }

        public String toString() {
            Thread o = sync.getOwner();
            return super.toString() + ((o == null) ?
                                       "[Unlocked]" :
                                       "[Locked by thread " + o.getName() + "]");
        }

        // 查询当前线程是否持有此写锁定
        public boolean isHeldByCurrentThread() {
            return sync.isHeldExclusively();
        }

        // 返回当前线程持有的锁数量
        public int getHoldCount() {
            return sync.getWriteHoldCount();
        }
    }

上面两段代码对比之后其实差别不大,而读锁的实现和ReentrantLock里面的实现几乎相同,都是使用了AQS的acquire/release操作。

二 写锁解析

2.1 lock()

首先我们先看看我们的老朋友:

		// 争夺锁
        public void lock() {
            sync.acquire(1);
        }

经过这么久的学习大家应该已经了解这东西的套路了吧,没错,这个肯定是调用了AQS中的acquire(Int)方法,在其中先尝试获取锁,如果获取失败就将当前线程封装为结点并且挂起,而尝试获取锁的方法由当前类实现,结点的管理就归功于AQS的CLH队列啦。几乎所有争夺锁的操作都千篇一律是这样的。不过相对于ReentrantLock锁来说,这里的实现不是看fairSync还是noFairSync,而是Sync

这里贴一段熟悉的代码:

	public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

这里说一下为什么对与tryAcquire(Int)要分fairSyncnoFairSync的实现,其实差别只有一个:那就是fairSync在使用CAS竞争锁之前会使用!hasQueuedPredecessors()来判断下当前线程是否在AQS头部。没错。。你没看错。。tryAcquire(int acquires)nonfairTryAcquire(int acquires)两段那么长的代码差别只有一个。。

而在ReentrantReadWriteLock中它的实现就简洁很多了,下面让我们看一下:

		protected final boolean tryAcquire(int acquires) {
            // 当前线程
            Thread current = Thread.currentThread();
            // 当前锁个数
            int c = getState();
            // 写锁的个数
            int w = exclusiveCount(c);
            // 该锁已被占有
            if (c != 0) {
                // c != 0 && w == 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;
        }

相比于ReentrantLock的实现,主要多加了一个对读锁的判断,在读锁上锁的情况下,写锁是不能够进入的。除此之外的一个不同点就是writerShouldBlock(),这个看意思大概是判断是否需要堵塞。

看下ReentrantLock的两个实现:

				// 公平锁
				if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
				// 非公平锁
				if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }

这样大家应该猜到了吧,这个writerShouldBlock()就是用来区分是否需要使用!hasQueuedPredecessors()方法的,让我们看下他怎么实现的:

在Sync内部类中:

abstract boolean writerShouldBlock();

将其定义为一个抽象方法,在fairSyncnoFairSync子类中重写:

		// 公平锁
		final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
		// 非公平锁
		final boolean writerShouldBlock() {
            return false; // 总是无需堵塞
        }

有没有感觉设计得十分巧妙。。反正我是这么觉得的。

2.2 lockInterruptibly()

继续看下去之前先猜猜他是怎么实现的。这个方法跟lock(Int)方法有何区别?

下面公布答案:

	public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

同理,只有一点区别:一开始会检查线程是否被中断,而且doAcquireInterruptibly(arg)里面已经包含有了addWaiter(Node.EXCLUSIVE)方法,而里面的实现与acquireQueued(addWaiter(Node.EXCLUSIVE), arg))的区别则是,前者当当前结点的线程被中断时,会抛出中断异常;而后者会在获取锁成功后真正中断该线程,此时不会抛出异常。

2.3 tryLock( )

        // 除了缺少对writerShouldBlock的调用之外,这与tryAcquire的效果相同。
        final boolean tryWriteLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c != 0) {
                int w = exclusiveCount(c);
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
            }
            if (!compareAndSetState(c, c + 1))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

仅仅尝试一次获取锁的操作,不能保证一定能够获取到锁。除了缺少对writerShouldBlock的调用之外,这与tryAcquire的效果相同。

下面还有一个加了时间限定的实现:

		// 在规定时间内获取锁
        public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireNanos(1, unit.toNanos(timeout));
        }

还是熟悉的配方还是熟悉的味道。。又是AQS的一个方法,重写tryAcquire(Int)就完事啦~注意这里直接是中断则抛出异常的,记得catch。

	public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
                doAcquireNanos(arg, nanosTimeout);
    }

2.4 unlock()

		// 释放锁
        public void unlock() {
            sync.release(1);
        }

还是AQS中的方法,主要是重写了tryRelease(Int)方法,AQS内部实现了CHL队列:

	// 释放持有的锁,用于实现unlock操作
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            // 成功释放锁,即已将锁的state状态位置为0
            // 看AQS队列中的头结点是否为空并且能否被唤醒,如果可以的话就唤醒继任节点
            Node h = head;
            if (h != null && h.waitStatus != 0)
                // 唤醒头结点的后继结点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

重点是tryRelease(arg)对于释放锁条件的判断以及后续的操作:

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

ReentrantLock类相比只多了个对状态位的位操作,具体操作都是减少指定的重入锁的数量。

2.5 other

		// 得到等待队列Condition
        public Condition newCondition() {
            return sync.newCondition();
        }
		// 查询当前线程是否持有此写锁定
        public boolean isHeldByCurrentThread() {
            return sync.isHeldExclusively();
        }

        // 返回当前线程持有的锁数量
        public int getHoldCount() {
            return sync.getWriteHoldCount();
        }

三 读锁操作

看完写锁操作再来看读锁操作会简单很多,在一些地方上可能会跳过,如果有看不懂的地方可以查阅一下之前的文章,都是会有说明的。

3.1 HoldCounter

在看读锁操作之前我们要先看一下HoldCounter这个类:

		// 记录当前线程持有共享锁的数量,这个数量必须要与线程绑定在一起,否则操作其他线程锁就会抛出异常。
        // 每线程读取保持计数的计数器。维持为ThreadLocal;缓存在cachedHoldCounter中
        static final class HoldCounter {
            int count = 0; // 计数器
            final long tid = getThreadId(Thread.currentThread());  // 线程编号
        }

在这里存储了一个计数器count跟一个线程编号tid,该类的用处是记录当前线程持有共享锁的数量,通过该类可以实现读锁即共享锁的可重入。但是我们根据这两个数据无法将HoldCounter 与线程绑定起来,因此JUC使用了一个ThreadLocal将该类与线程绑定起来。具体代码如下:

ThreadLocalHoldCounterSync 的内部静态类:

static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
    @Override
    public HoldCounter initialValue() {
        return new HoldCounter();
    }   
}

此时我们便通过 ThreadLocalHoldCounter 类,将HoldCounter 与线程绑定起来。此时我们使用 HoldCounter 持有的线程编号,这样在释放锁的时候就能知道 ReadWriteLock 里面缓存的上一个读取线程(cachedHoldCounter)是否是当前线程。这样做的好处是可以减少ThreadLocal.get() 方法的次调用数,因为这也是一个耗时操作。

需要说明的是这样HoldCounter 绑定线程编号而不绑定线程对象的原因是,避免 HoldCounterThreadLocal 互相绑定而导致 GC 难以释放它们(尽管 GC 能够智能的发现这种引用而回收它们,但是这需要一定的代价)。

下面说一些会涉及到HoldCounter的字段或方法,方便下面写锁的阅读:

		// 当前线程的holdCounter
        private transient ThreadLocalHoldCounter readHolds;
		// 最后一个获得读锁的线程的 HoldCounter 的缓存对象
        private transient HoldCounter cachedHoldCounter;
		// 第一个获取读锁的线程
        private transient Thread firstReader = null;
        // 第一个获取读锁的重入数
        private transient int firstReaderHoldCount;

		// 这里的构造函数会给readHolds即当前线程的holdCounter初始化,因此下面会有直接调用的场景
        Sync() {
            readHolds = new ThreadLocalHoldCounter();
            setState(getState()); // ensures visibility of readHolds
        }

3.2 lock()

		// 争夺读锁
        public void lock() {
            sync.acquireShared(1);
        }
	public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

看吧,还是只有tryAcquireShared(arg)是被重写的。

不过此处的共享锁可跟咋们之前学习到的共享锁有所不同,此处共享锁支持了可重入,咋们来看下具体是怎么实现的吧:

		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);
            // 检查持锁线程head后续节点s是否为写锁,若真则返回true
            // 在不超过共享锁数量的前提下更新共享锁数量
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                // 此处已是修改状态位成功

                // 若之前共享锁的状态位为0,即没有线程占有锁,则将当前线程设置为firstReader
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    // 如果获取读锁的线程为第一次获取读锁的线程,则重入数+1
                    firstReaderHoldCount++;
                } else {
                    // 拿到当前线程的holdCount
                    HoldCounter rh = cachedHoldCounter;
                    // 判断holdCount是否为空或是否正确
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    // 增加当前线程的共享锁重入数量
                    rh.count++;
                }
                return 1;
            }
            // 处理CAS操作失败的自旋,已经实现重入性
            return fullTryAcquireShared(current);
        }

具体步骤如下:

  1. 如果写锁已经被获取并且获取写锁的线程不是当前线程的话,当前线程获取读锁失败返回-1。此处说明了写锁是可以降级为读锁的。
  2. 尝试CAS更新状态位,更新成功则跳3,否则跳4
  3. 利用与线程绑定的HoldCounter对共享锁的重入进行判定。
  4. 调用fullTryAcquireShared(current);处理CAS操作失败的自旋。

按照写锁的模式来看下公平锁与非公平锁对于readerShouldBlock()的实现:

非公平锁:

		// 非公平锁,若当前占有锁为写锁,则应该堵塞该线程
		final boolean readerShouldBlock() {
            // 检查持锁线程head后续节点s是否为写锁,若真则返回true
            return apparentlyFirstQueuedIsExclusive();
        }
	// 检查持锁线程head后续节点s是否为写锁,若真则返回true
    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&     // head非空
                (s = h.next) != null &&  // 后继节点非空
                !s.isShared() &&         // 后继节点不是共享线程
                s.thread != null;        // 后继节点线程非空
    }

公平锁:

		// 判断在AQS的CLH队列中当前线程前方是否有其他节点,若有则应该堵塞该线程
		final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }	

fullTryAcquireShared(current);这个方法与上面方法的相似度很高,增加了对需要阻塞时对于重入锁的判断,是前者自旋重试的逻辑。

		// 读取的完整版本,处理CAS未命中和tryAcquireShared中未处理的重入读取。
        final int fullTryAcquireShared(Thread current) {
            // 此代码与tryAcquireShared中的代码部分冗余,但总体上更简单,因为不会使tryAcquireShared与重试和延迟读取保持计数之间的交互变得复杂。
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                    // 读锁需要阻塞,判断是否当前线程已经获取到读锁
                } else if (readerShouldBlock()) {
                    //列头为当前线程
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();  // 计数为 0 ,说明没得到读锁,清空线程变量
                            }
                        }
                        // 说明没得到读锁
                        if (rh.count == 0)
                            return -1;
                    }
                }

                // 读锁超出最大范围
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");

                // CAS设置读锁成功
                // 此处跟tryAcquireShared(Int)内的实现差不多一样
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    // 如果是第一次获取读锁,则更新firstReader和firstReaderHoldCount
                    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;
                }
            }
        }

3.3 lockInterruptibly()

同上面写锁的操作差不多,主要是重写了tryAcquireShared(arg)方法:

		// 以共享锁的模式竞争锁,线程被中断时抛出异常
        public void lockInterruptibly() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
        }
	// 以共享锁的模式竞争锁,线程被中断时抛出异常
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 尝试竞争共享锁,返回-1时即竞争失败
        if (tryAcquireShared(arg) < 0)
            // 以共享可中断模式获取。循环CAS直到成功获取或线程被中断
            doAcquireSharedInterruptibly(arg);
    }

具体可以参考写锁跟以前有关共享锁的文章,比如CountDownLock的操作。

3.4 tryLock( )

		// 尝试争夺锁
        public boolean tryLock() {
            return sync.tryReadLock();
        }
		// 除了缺少对readerShouldBlock的调用之外,这与tryAcquireShared的效果相同。
        final boolean tryReadLock() {
            Thread current = Thread.currentThread();
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0 &&
                    getExclusiveOwnerThread() != current)
                    return false;
                int r = sharedCount(c);
                if (r == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (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 != getThreadId(current))
                            cachedHoldCounter = rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                    }
                    return true;
                }
            }
        }

不多说,贴出来,看过tryAcquireShared()的可以跳过。

		// 尝试在有限时间内争夺锁
        public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
        }

3.5 unlock()

又到了重点戏了:

		// 尝试释放此锁定
        public void unlock() {
            sync.releaseShared(1);
        }	
	// 以共享模式发布。如果tryReleaseShared返回true,则通过解除阻塞一个或多个线程来实现。
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

大家伙学到这里应该知道了。。重写tryReleaseShared(arg)就完事啦,对于CLH的操作就留给AQS实现吧。

		protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            // 如果想要释放锁的线程为第一个获取锁的线程
            if (firstReader == current) {
                // 若之前仅获取了一次,则需要将firstReader 设置null
                // 如果该锁之前有重入的,则firstReaderHoldCount - 1
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                // 获取rh对象
                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;
            }
            // CAS更新同步状态
            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;
            }
        }

看到这里大家应该也注意到有个奇怪的参数混入其中了,unused。没错。。顾名思义,它真的没有被用到。

2.5 other

		// ReadLocks是共享锁,不需要支持Condition条件队列,因此调用会直接抛出异常
        public Condition newCondition() {
            throw new UnsupportedOperationException();
        }

        public String toString() {
            int r = sync.getReadLockCount();
            return super.toString() +
                "[Read locks = " + r + "]";
        }

四 总结

这次的ReentrantReadWriterLock的解析到这里就先告一段落了,咋们就来进行一段小小的总结了。

从这个类的学习中大家应该会感受到AQS设计的美妙之处了,将CLH队列的维护统统交给AQS来实现,而子类呢,则负责实现对于获取锁以及释放锁操作的判断就好了,十分方便。

ReentrantReadWriterLock涉及到了两种锁,写锁与读锁,也可以称之为独占锁与共享锁,不过此处的共享锁利用了内部的实现类HoldCounter对重入锁的数量进行计数以及ThreadLocal将前者与线程绑定起来。也算是另辟蹊径了。

读写锁的主要特性:

  1. 公平性:支持公平锁和非公平锁。并且简化成了对读写锁是否应该阻塞的判断。
  2. 重入性:支持重入。读写锁最多支持65535个递归写入锁和65535个递归读取锁。
  3. 锁降级:遵循获取写锁、获取读锁在释放写锁的次序,写锁能够降级成为读锁
  4. 锁升级:读取锁是不能直接升级为写入锁的。因此获取一个写入锁需要释放所有读取锁。
    参考文章:
    深入浅出 Java Concurrency
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值