图解java.util.concurrent并发包源码系列——深入理解ReentrantReadWriteLock读写锁,看完可以吊打面试官

news2025/2/28 7:59:54

图解java.util.concurrent并发包源码系列——深入理解ReentrantReadWriteLock读写锁,看完可以吊打面试官

  • ReentrantReadWriteLock的作用
  • ReentrantReadWriteLock的原理
  • ReentrantReadWriteLock源码解析
    • 构造方法
    • 获取写锁和读锁对象
    • 计算读锁被持有数和写锁被持有数的位移运算
    • 获取锁和释放锁的大体流程
      • tryAcquireShared
        • fullTryAcquireShared
      • tryReleaseShared
      • tryAcquire
      • tryRelease
      • 总结

往期文章:

  • 人人都能看懂的图解java.util.concurrent并发包源码系列 ThreadPoolExecutor线程池
  • 图解java.util.concurrent并发包源码系列,原子类、CAS、AtomicLong、AtomicStampedReference一套带走
  • 图解java.util.concurrent并发包源码系列——LongAdder
  • 图解java.util.concurrent并发包源码系列——深入理解AQS,看完可以吊打面试官
  • 图解java.util.concurrent并发包源码系列——深入理解ReentrantLock,看完可以吊打面试官

上一篇文章,介绍了ReentrantLock的作用和源码,了解到ReentrantLock是如何通过AQS去实现它的可重入锁的功能的。这次我们再了解另一个并发工具类ReentrantReadWriteLock读写锁,我们从ReentrantReadWriteLock的作用到源码,了解如何通过AQS实现更复杂的并发工具类。因为ReentrantReadWriteLock使用到AQS实现自身的功能,所以需要对AQS有一定的了解,本篇文章同样不会对AQS做过多的介绍,可以通过往期的文章(图解java.util.concurrent并发包源码系列——深入理解AQS,看完可以吊打面试官)了解AQS的作用和原理。

ReentrantReadWriteLock的作用

ReentrantReadWriteLock是读写锁,读写锁的作用就是可以实现读读并发,读写互斥。如果我们只是想对临界资源进行读操作,但是不做修改,那么我么可以获取读锁,读锁不会阻塞其他线程获取读锁,只会阻塞其他线程获取写锁,用于保证获取读锁期间临界资源不会被修改。如果我们对临界资源进行写操作,那么我们可以获取写锁,写锁会阻塞其他的线程获取写锁和读锁。

在读多写少的场景下,读写锁的并发度是比互斥锁(ReentrantLock)的并发度高的,互斥锁无论做的是读操作还是写操作,获取到锁后都会阻塞其他线程。

在这里插入图片描述

ReentrantReadWriteLock里面包含了读锁ReadLock和写锁WriteLock两把锁。当我们要加读锁时,我们可以通过ReentrantReadWriteLock获取到ReadLock,然后调用ReadLock的lock方法,就可以加读锁,调用ReadLock的unlock方法就可以释放读锁。当我们要加写锁时,我们可以通过ReentrantReadWriteLock获取到WriteLock,然后调用WriteLock的lock方法,就可以加写锁,调用WriteLock的unlock方法,就可以释放写锁。

在这里插入图片描述

ReentrantReadWriteLock的原理

既然只有读锁和读锁是可以并发的,读锁和写锁,写锁和写锁互斥,那读写和写锁间就要互相感知,也就是要知道对方是否被某些线程持有着,比如获取读锁时要判断写锁是否被其他线程持有,如果写锁被被其他线程持有,那么读锁就不能加锁成功。

但是读锁和写锁时两个锁,如何做到互相感知呢?那就是使用共享内存变量,而这个变量不是别的,正是AQS的state变量。

ReentrantReadWriteLock内部也是有一个Sync的内部类的,这个内部类也继承了AQS,在创建ReentrantReadWriteLock时,同时创建Sync、ReaderLock和WriteLock,并且把Sync作为ReaderLock和WriteLock的构造参数传递给ReaderLock和WriteLock,ReaderLock和WriteLock可以通过Sync获取AQS中的state变量,得知对方是否有被持有。

在这里插入图片描述

那一个变量,如何同时供ReaderLock读锁和WriteLock写锁使用呢?state要同时记录读锁的被持有数和写锁的被持有数,因此要使用位运算。ReentrantReadWriteLock把state切成两半,高16位用于记录读锁的被持有数,低16位用于记录写锁的被持有数。那么ReaderLock和WriteLock就可以通过对state变量做位运算,得知对方是否有被持有。

在这里插入图片描述

ReentrantReadWriteLock源码解析

构造方法

下面开始阅读ReentrantReadWriteLock的源码,首先是ReentrantReadWriteLock的构造方法。

    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

ReentrantReadWriteLock的构造方法接收一个boolean类型的参数fair,如果fair是true,表示ReentrantReadWriteLock是公平锁形式的读写锁,那么内部的Sync的类型使用FairSync,如果fair是false,表示ReentrantReadWriteLock是非公平锁形式的读写锁,那么内部的Sync的类型使用NonfairSync。

ReentrantReadWriteLock的构造方法接下来会创建ReadLock对象和WriteLock对象,并以自身作为构造参数。

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

然后ReadLock和ReadLock的构造方法都会保存ReentrantReadWriteLock的Sync对象到自身内部,与我们上面说的一致。

在这里插入图片描述

获取写锁和读锁对象

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

调用ReentrantReadWriteLock的writeLock方法,可以获取写锁;调用ReentrantReadWriteLock的readLock方法,可以获取读锁。

在这里插入图片描述

计算读锁被持有数和写锁被持有数的位移运算

而计算读锁被持有数和写锁被持有数的方法,则是在Sync类内部,ReadLock和WriteLock可以通过Sync提供的方法判断对方的被持有数,从而判断自己是否可以尝试加锁。

    abstract static class Sync extends AbstractQueuedSynchronizer {
        
        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;

        // 通过位移运算获取读锁被持有数(state无符号右移16位)
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        // 通过位移运算获取写锁被持有数(state与写锁掩码按位与运算,写锁掩码高16位为0,低16位为1)
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

		// 。。。。。。下面代码省略
	}

可以看到Sync的sharedCount方法用于获取读锁被持有数,exclusiveCount方法用于获取写锁被持有数,这两个方法的int类型参数c都是state。

sharedCount方法将state进行无符号右移16位(SHARED_SHIFT值固定为16),右移16位后,正好把原先低16位的写锁被持有数抹去,然后高16位的读锁被持有数移到了低16位,那么此时sharedCount方法的返回值就是读锁被持有数。

exclusiveCount则是通过state和一个掩码EXCLUSIVE_MASK做按位与运算,把高16位的读锁被持有数抹去,EXCLUSIVE_MASK的高16位全是0,低16位全是1,那么state和EXCLUSIVE_MASK做按位与运算之后,高16位正好都会变为0,那么此时exclusiveCount方法的返回值就是写锁被持有数。

在这里插入图片描述

获取锁和释放锁的大体流程

下面看一看获取锁和释放锁的大体流程,先不看细节,对整体流程有个印象。

ReadLock的lock方法,是直接调用sync的acquireShared方法,acquireShared方法是AQS提供的一个模板方法,以共享模式获取锁。

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

ReadLock的unlock方法,直接调用sync的releaseShared方法,releaseShared方法是AQS提供的一个模板方法,用于共享模式获取锁情况下释放锁。

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

WriteLock的lock方法,直接调用sync的acquire方法,acquire方法是AQS提供的一个模板方法,以独占模式获取锁。

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

WriteLock的unlock方法,直接调用sync的release方法,release方法是AQS提供的一个模板方法,用于独占模式获取锁情况下释放锁。

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

acquireShared方法、releaseShared方法、acquire方法、release方法,这些方法都是AQS内部提供的模板方法。acquireShared方法会调用tryAcquireShared方法,releaseShared方法会调用tryReleaseShared方法,acquire方法会调用tryAcquire方法,release方法会调用tryRelease方法,这些方法都要继承AQS的子类去实现,而在ReentrantReadWriteLock中,这些方法都在Sync类的内部。

    abstract static class Sync extends AbstractQueuedSynchronizer {
	
		// 。。。。。。省略上面代码
        
        abstract boolean readerShouldBlock();
        
        abstract boolean writerShouldBlock();

        protected final boolean tryRelease(int releases) {...}

        protected final boolean tryAcquire(int acquires) {...}

        protected final boolean tryReleaseShared(int unused) {...}

        protected final int tryAcquireShared(int unused) {...}

		// 。。。。。。省略下面代码
	}        

那么既然tryAcquireShared、tryReleaseShared、tryAcquire、tryRelease都在Sync内部,那Sync类的子类FairSync和NonfairSync内部有啥呢?

    static final class NonfairSync extends Sync {
        final boolean writerShouldBlock() {...}
        final boolean readerShouldBlock() {...}
    }

    static final class FairSync extends Sync {
        final boolean writerShouldBlock() {...}
        final boolean readerShouldBlock() {...}
    }

ReentrantReadWriteLock的FairSync和NonfairSync内部相对于ReentrantLock的FairSync和NonfairSync就简单很多了,就是实现了Sync定义的两个抽象方法writerShouldBlock方法和readerShouldBlock方法。writerShouldBlock方法会在Sync的tryAcquire方法内部被调用,而readerShouldBlock方法会在Sync的tryAcquireShared方法内部被调用。

那么大体逻辑就是这样:

在这里插入图片描述

那么下面我们就着重对这几个方法进行分析。

tryAcquireShared

当我们调用ReadLock的lock方法获取读锁时,ReadLock的lock方法直接调用AQS的acquireShared方法,acquireShared的acquire方法会调用子类实现的tryAcquireShared方法,然后就会进入到Sync的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);
            // readerShouldBlock():
            // 		如果是非公平锁,检查队列中的第一个节点是否是等待获取写锁的线程,如果是,那么返回true,当前线程次数尝试获取读锁失败;
            //		如果是公平锁,看队列中是否有节点在排队,如果有,那么当前线程不能获取读锁(除非当前线程也在队列中,并且刚好轮到它)。
            // r < MAX_COUNT:防止写锁超过最大的获取数
            // compareAndSetState(c, c + SHARED_UNIT):上面两个条件都通过,那么尝试CAS更新state,因为读锁的持有数记录在state的高16位,所以这里不是+1,而是+SHARED_UNIT(一个读锁获取单位)
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                // 下面是读锁获取成功后,修改读锁的持有数
                if (r == 0) {
                	// 当前没有线程获取读锁,当前线程是第一个获取读锁的线程,记录到firstReader,然后当前线程的读锁持有数记录到firstReaderHoldCount
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                	// 当前线程是第一个获取读锁的线程,当前线程的读锁持有数记录到firstReaderHoldCount
                    firstReaderHoldCount++;
                } else {
                	// 当前线程不是第一个获取读锁的线程,读锁持有数记录到一个ThreadLocal变量中欧冠
                    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;
            }
            // CAS失败,那么调用fullTryAcquireShared方法进行自旋
            return fullTryAcquireShared(current);
        }

先上一张图,描绘一下上面tryAcquireShared方法的代码流程:

在这里插入图片描述

tryAcquireShared方法的第一步,就是先看一下当前有没有线程获取了写锁,并且获取写锁的线程不是当前线程。如果是的话,那么本次尝试获取读锁就立刻返回失败。

如果当前没有线程获取写锁呢?那么可以继续往下走,调用readerShouldBlock()方法判断当前线程获取读锁是否应该被阻塞,我们进去看看readerShouldBlock()方法的逻辑。

在这里插入图片描述

可以看到readerShouldBlock()方法有两个版本的实现,因为tryAcquireShared方法是在Sync类内部,所以此时readerShouldBlock()方法会调用到Sync子类的实现,FairSync代表公平锁,NonfairSync代表非公平锁。

NonfairSync#readerShouldBlock:

        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
        
	    final boolean apparentlyFirstQueuedIsExclusive() {
	        Node h, s;
	        // 判断队列中的第一个节点的线程,是否是非共享模式,也就是等待获取写锁的
	        return (h = head) != null &&
	            (s = h.next)  != null &&
	            !s.isShared()         &&
	            s.thread != null;
	    }

NonfairSync的readerShouldBlock方法,就是判断队列中的第一个节点的线程,是否在等待获取写锁的,如果是的话,那么不能让当前线程获取读锁,为什么呢?因为如果这里不拦住,就会造成写饥饿。想象一下,此时源源不断的有线程过来获取读锁,那么是不是这个要获取写锁的线程,永远都无法获取到?

FairSync#readerShouldBlock:

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

FairSync的readerShouldBlock就相对简单了,就是调用hasQueuedPredecessors()方法进行判断,这个方法我们上一篇文章已经分析过,就是判断队列中是否有等待获取锁的线程。如果队列中有等待获取锁的线程,那么当前线程也不能获取读锁,要去排队。除非当前线程已经在排队,并且刚好轮到它了,那么当前线程可以尝试获取读锁。

回到tryAcquireShared方法,接下来是判断读锁当前被持有的次数,是否还没满。如果还没满,那么当前线程可以尝试获取读锁;如果满了,那么当前线程也是不能获取读锁的。因为state是一个int类型的变量,并且要同时记录读锁的被持有数和写锁的被持有数,只有16位记录读锁被持有的次数,超了就记录不下了。

上面两个条件都通过的话,那么就可以调用compareAndSetState(c, c + SHARED_UNIT)方法,尝试CAS修改state变量。c就是state变量,尝试修改为“c + SHARED_UNIT”而不是“c + 1”,因为state用高16位记录读锁被持有的次数,所有不能+1。

如果CAS修改成功了,那么就表示获取读锁成功了。在方法返回前,要更新当前线程持有读锁的次数,如果它是第一个获取读锁的线程,那么可以直接记录到firstReaderHoldCount这个成员变量中,并且用firstReader记录当前线程是第一个获取读锁的线程。如果当前线程不是第一个获取读锁的线程,那么就要记录到ThreadLocal中。

如果CAS失败,或者前两个条件就没满足,那么就调用fullTryAcquireShared(current)方法,在里面进行自旋重试。

fullTryAcquireShared

        final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) { // 自旋
                int c = getState();
                // 如果有别的线程获取了写锁,获取读锁失败
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                // 再次调用readerShouldBlock()方法判断是否要阻塞当前线程获取读锁
                } else if (readerShouldBlock()) {
                    if (firstReader == current) {
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                // 判断读锁被持有数是否要超了
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 尝试CAS修改state
                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;
                    }
                    return 1;
                }
            }
        }

可以看到就是一个自旋,自旋里面的逻辑跟外面的tryAcquireShared方法是非常相似的。也是判断如果有别的线程获取了写锁,那么当前线程获取读锁失败;如果没有线程持有写锁,那么调用readerShouldBlock()方法判断是否要阻塞当前线程获取读锁,如果readerShouldBlock()方法返回true,那么当前线程本轮循环不能获取读锁;如果readerShouldBlock()方法返回false,那么判断读锁的被持有数是否已经满了,如果满了,就抛一个异常;这些条件都通过了,才尝试使用CAS的方式更新state变量,如果更新成功了,就修改当前线程的读锁持有数,返回获取读锁成功;如果前面的这些条件没有满足,或者CAS更新失败,那么本轮循环获取读锁失败,进入下一轮循环。

在这里插入图片描述
tryAcquireShared方法分析完毕。

tryReleaseShared

当我们调用ReadLock的unlock释放读锁时,会调用AQS的releaseShared方法,AQS的releaseShared方法会调用Sync的tryReleaseShared方法。

Sync#tryReleaseShared:

        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            // 1.更新当前线程的读锁持有数。
            if (firstReader == current) {
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                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;
            }
            // 2.死循环进行CAS更新state变量,直到更新成功为止。如果state等于0,会返回true表示锁全部释放完毕,如果队列中有等待获取写锁的,可以尝试获取写锁了;如果state不等于0,那么还有线程没有释放锁。
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

Sync的tryReleaseShared方法相对简单,就是两大步。第一步是更新当前线程的读锁持有数,如果当前线程是第一个获取读锁的线程,那么就更新firstReaderHoldCount变量,firstReaderHoldCount为0的话,会把firstReader (第一个获取读锁的线程)置空;如果当前线程不是第一个获取读锁的线程,那么就更新ThreadLocal。第二步是自旋CAS更新state变量,更新成功后如果state等于0了,会返回true,表示锁完全释放(此时没有线程持有锁),如果有线程想获取写锁的话,可以尝试获取写锁;更新成功后如果state不等于0,那么返回false,表示还有线程持有锁。在这里插入图片描述

tryReleaseShared方法分析完毕。

tryAcquire

当我们通过ReentrantReadWriteLock的writeLock()方法获取了WriteLock对象,可以调用WriteLock对象的lock方法获取读锁,lock方法会调用AQS的acquire方法,然后acquire方法会调用Sync对象的tryAcquire方法尝试获取写锁。

        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            // 获取AQS中的state变量
            int c = getState();
            // 获取写锁被持有数
            int w = exclusiveCount(c);
            // state不为0,表示有线程持有锁
            if (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
                setState(c + acquires);
                return true;
            }
            // writerShouldBlock()判断是否要阻塞当前线程获取写锁
            // compareAndSetState(c, c + acquires) CAS尝试更新state,更新成功表示获取写锁成功
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            // 设置当前线程为独占锁的线程
            setExclusiveOwnerThread(current);
            return true;
        }

首先是判断state是否不等于0,如果state不等于0,那么表示当前已经有线程获取了锁,如果有线程获取了读锁,或者有别的线程获取了写锁,那么当前线程获取写锁失败,因为写锁与写锁之间、写锁与读锁之间都是互斥的。如果state不等于0,但是是当前线程已经持有了写锁,那么当前线程是可以继续获取写锁的,也就是锁重入,而且修改state变量只需要调用setState方法即可,不需要CAS,因为当前线程已经持有了写锁,其他线程是无法修改state的,只有当前线程可以修改state。

如果state等于0,那么当前没有线程持有锁,然后就调用writerShouldBlock()方法判断当前线程是否需要被阻塞,如果不需要阻塞当前线程获取写锁的话,那么当前线程可以尝试CAS修改state变量,如果修改state变量成功,代表当前线程成功获取了写锁,那么修改当前线程为独占锁的线程。

在这里插入图片描述

我们再看看writerShouldBlock()方法的逻辑。

在这里插入图片描述
NonfairSync#writerShouldBlock

        final boolean writerShouldBlock() {
            return false;
        }

在非公平锁的情况下,只要当前没有线程已经持有读锁,那么永远不会阻塞写锁的获取。这是防止写饥饿的一种处理,只要当前没有线程持有读锁,当前线程就可以尝试获取写锁。如果不这样处理的话,有可能会有大量的线程抢先获取了读锁,那么当前线程就有可能迟迟获取不了写锁。

FairSync#writerShouldBlock

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

FairSync的writerShouldBlock方法和readerShouldBlock方法一样的逻辑,都是调用hasQueuedPredecessors()判断队列中是否有线程在排队,如果有的话,那么当前线程也只能去排队。

在这里插入图片描述

tryAcquire方法的整体逻辑:

在这里插入图片描述

tryAcquire方法分析完毕。

tryRelease

当我们调用WriteLock对象的unlock方法释放读锁时,unlock方法会调用AQS的release方法,然后release方法会调用Sync的tryRelease方法。

        protected final boolean tryRelease(int releases) {
        	// 当前线程不是独占锁的线程,表示当前线程没有获取写锁,是不需要释放写锁的,抛异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            // 更新后的state
            int nextc = getState() - releases;
            // 更新后的state中写锁持有数为0,表示当前线程彻底释放了写锁,那么free为true
            boolean free = exclusiveCount(nextc) == 0;
            // 当前线程彻底释放了写锁,设置当前占有锁的线程为null
            if (free)
                setExclusiveOwnerThread(null);
            // state更新之前,state中的写锁持有数是不为0的,其他线程是进不来的,所以这里直接setState即可,不需要CAS
            setState(nextc);
            return free;
        }

首先判断当前线程是否是独占锁的线程,如果不是的话,表示当前线程是没有获取写锁的,那么就不需要释放写锁,因此会抛出一个异常。

然后判断当前线程在本次释放写锁之后,写锁是否被完全释放,也就是所有重入的锁是否都释放回去了,如果是,那么修改当前独占锁的线程为null。

最后使用AQS提供的setState方法修改state变量。这里不需要CAS,是因为即便是setState修改后state为0,在setState之前,state中的写锁持有数都是不为0的,那么其他线程是进不来的,因此还是只有当前线程可以修改state。

在这里插入图片描述

tryRelease方法分析完毕。

总结

整个ReentrantReadWriteLock的获取锁和释放锁的大体流程,到这里就分析完毕了,最后上一张大图:

在这里插入图片描述

简单概括的话,其实就是在获取读锁的时候,判断是否有其他线程获取写锁,如果有,那么就是冲突的,否则就可以获取读锁。而获取写锁的时候,就是判断一下是否有其他线程获取了读锁或者写锁,如果有,那么就是冲突,否则就可以获取写锁。如果可以获取写锁或者读锁,那么就会更新state变量。而释放锁的时候,就是反向更新state。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/862238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

爆笑,打地鼠的极限,PyAutoGUI的开始~

游戏地址&#xff1a;http://www.4399.com/flash/178030_3.htm 视频教程地址&#xff1a;https://www.bilibili.com/video/BV1gm4y1x7QW/ 介绍 当提到自动化控制鼠标和键盘的Python库时&#xff0c;pyautogui是一个不可忽视的工具。它为用户提供了简单而强大的功能&#xff…

【JAVA基础】- 同步非阻塞模式NIO详解

【JAVA基础】- 同步非阻塞模式NIO详解 文章目录 【JAVA基础】- 同步非阻塞模式NIO详解一、概述二、常用概念三、NIO的实现原理四、NIO代码实现客户端实现服务端实现 五、同步非阻塞NIO总结 一、概述 NIO&#xff08;Non-Blocking IO&#xff09;是同步非阻塞方式来处理IO数据。…

主题模型分析-【LDA】

主题模型能够自动将文本语料库编码为一组具有实质性意义的类别。这些类别称为主题。 主题模型分析的典型代表就是本篇文章将要介绍的隐含迪利克雷分布&#xff0c;也就是LDA。 假设我们有一个文档或者新闻的集合&#xff0c;我们想将他们分类为主题。 我们设置好主题数量后&am…

Java中创建对象的方式

文章目录 一、使用new关键字二、使用反射机制三、使用clone方法四、使用反序列化 一、使用new关键字 下面是使用 new 关键字创建对象的示例代码&#xff0c;同时演示了如何调用不同构造函数的方式&#xff1a; public class Person {private String name;private int age;pub…

超人PDF解密助手:pdf怎么解除编辑权限

PDF文件带有编辑权限&#xff0c;想要取消PDF文件的限制&#xff0c;该如何操作&#xff1f; 打开PDF编辑器&#xff0c;点击工具栏中的文件&#xff0c;选择属性按钮&#xff0c;进入到熟悉感界面之后&#xff0c;点击安全&#xff0c;然后我们点击权限下拉框&#xff0c;选择…

LaTeX基础学习笔记

LaTeX是一个文本编辑器。其类似于markdown&#xff0c;使用特殊标记和代码来修改文本格式&#xff0c;创建特殊字符等。可以使用overleaf在线LaTex编辑器编写LaTeX并转换为pdf文件&#xff08;https://www.overleaf.com/&#xff09; 同时推荐一个网站http://detexify.kirelab…

Mysql 复杂查询丨联表查询

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; JOIN&#xff08;联表查询&#xff09; 联表查询&#xff08;Join&#xff09;是一种在数据库中使用多个表进行关联查询的操作。它通过使用 JOIN 关键字将多个表连接在…

[保研/考研机试] KY163 素数判定 哈尔滨工业大学复试上机题 C++实现

题目链接&#xff1a; 素数判定https://www.nowcoder.com/share/jump/437195121691718831561 描述 给定一个数n&#xff0c;要求判断其是否为素数&#xff08;0,1&#xff0c;负数都是非素数&#xff09;。 输入描述&#xff1a; 测试数据有多组&#xff0c;每组输入一个数…

橡胶履带行业分析报告2023-2029

橡胶履带行业分析报告&#xff0c;2022年全球橡胶履带市场规模达到了19.2亿美元 橡胶履带是用橡胶和骨架材料制成的履带&#xff0c;它被广泛用于工程机械、农用机械和军用装备。橡胶履带行业产业链主要原材料包括橡胶、芯金、炭黑、钢丝、各类橡胶化学助剂等&#xff0c;上游…

python函数的传参

一、关键字参数 定义一个接受关键字参数的函数 def test_arg(**kargs):for key in kargs:print(f{key}{kargs[key]}) 这里的kargs是一个字典&#xff0c;但传入的参数是 **kargs, 而非 kargs. 可以试试&#xff1a; test_arg(A30,B"Chinese") 但你直接传入一个字…

将一组元素四舍五入到指定精度(小数位数)numpy.around()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 将一组元素四舍五入 到指定精度(小数位数) numpy.around() [太阳]选择题 关于以下代码说法错误的一项是? import numpy as np anp.array([21.3,9.67,8.5, -1.13, -2.67]) print(【显示】a为…

【Python机器学习】实验11 神经网络-感知器

文章目录 人工神经网络感知机二分类模型算法 1. 基于手写代码的感知器模型1.1 数据读取1.2 构建感知器模型1.3 实例化模型并训练模型1.4 可视化 2. 基于sklearn的感知器实现2.1 数据获取与前面相同2.2 导入类库2.3 实例化感知器2.4 采用数据拟合感知器2.5 可视化 实验1 将上面数…

学习笔记-JVM监控平台搭建

SpringBoot Actuator 1 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId> </dependency>2 开启配置 # 暴露所有的监控点【含Prometheus】 management.endpoin…

TIA博途软件无法搜索到CPU的解决办法汇总

TIA博途软件无法搜索到CPU的解决办法汇总 1. 检查电脑操作系统的兼容性 西门子对每个主要软件都会进行官方的兼容性测试过,其中重要一项是和 Windows 操作系统的兼容性,需要根据软件版本和 Windows 版本核对,如果不匹配,可能会有问题。 每个版本软件的安装条件可以参考以下…

网页爬虫中常用代理IP主要有哪几种?

各位爬虫探索者&#xff0c;你是否有想过在网页爬虫中使用代理IP来规避限制实现数据自由&#xff1f;在这篇文章中&#xff0c;作为一名IP代理产品供应商&#xff0c;我将为你揭示常见的网页爬虫代理IP类型&#xff0c;让你在爬虫的世界中游刃有余&#xff01; 一、免费公开代理…

Linux系统文件类型与文件权限

一、文件类型 &#xff08;1&#xff09;在windows系统中文件类型以文件的后缀名来区分&#xff0c;在Linux系统中文件类型不以后缀名来区分。注意编写c代码时必须写后缀名.c&#xff0c;不然C编译器不会编译该文件。 &#xff08;2&#xff09;在Linux系统中以文件的标志来区…

springboot在线小说阅读网站的设计与实现

网站首页&#xff1a; 用户登录/注册&#xff1a;用户注册时进行用户名及笔名存在的限制热门小说推荐&#xff1a;显示小说名及作者名&#xff0c;点击进入对应小说小说类别&#xff1a;玄幻&#xff0c;武侠&#xff0c;言情&#xff0c;历史等&#xff0c;点击对用分类到分类…

那些年的Java开发经验记录

Java同步锁(浅显易懂&#xff0c;精简讲解) 详细讲解可以看这篇文章Java对象锁和类锁全面解析&#xff08;多线程synchronized关键字&#xff09; 精简如下&#xff1a; 1.不管什么锁&#xff0c;都是属于对象锁(类也是一种对象) 2.一个对象只有一个锁 3.锁最大可以锁整个…

AcrelEMS-BP生物制药能效管理系统解决方案

安科瑞 崔丽洁 平台概述 AcrelEMS-BP生物制药能效管理系统集变电站综合自动化、电力监控、电能质量分析及治理、电气安全、能耗分析、照明控制、设备运维于一体。平台采用自动化、信息化技术和集中管理模式&#xff0c;对企业的生产、输配和消耗环节实行集中扁平化的动态监控和…

LTE鉴权失败原因分析

背景介绍&#xff1a;网络发鉴权加密申请后&#xff0c;收到用户响应后&#xff0c;同时又收到reject&#xff0c;reject code 是0x14&#xff0c;还不能确认是否鉴权参数设置问题引起的&#xff0c;欢迎各位专家来讨论及给建议。 reject code如下&#xff1a; 1、通过log&am…