JUC-ReentrantReadWriteLock锁源码分析

JUC-AQS原理篇
JUC-AQS源码篇
JUC-AQS的Condition之await和signal源码解析

JUC-CountDownLatch基础篇
JUC-CountDownLatch源码分析
JUC-Semaphore基础篇
JUC-Semaphore源码分析
JUC-ReentrantReadWriteLock锁基础篇
JUC-ReentrantReadWriteLock锁源码分析
JUC-ReentrantLock锁基础篇
JUC-ReentrantLock锁源码分析
JUC-CyclicBarrier基础篇
JUC-CyclicBarrier源码分析

建议阅读ReentrantReadWriteLock锁源码时,先去阅读JUC-ReentrantReadWriteLock锁基础篇涉及到的AQS源码可以参考JUC-AQS源码篇


ReentrantReadWriteLock是一个可重入的读写锁,里面有一把读锁与一把写锁。在同一时刻,读锁可以被多个线程同时共享持有,写锁只能由一个线程独占持有。读写互斥,写写互斥,读读可以并存

1.ReentrantReadWriteLock类结构图

在这里插入图片描述
ReentrantReadWriteLock内部有一个类Sync,Sync是一个继承了AbstractQueuedSynchronizer的静态抽象类Sync,还有一个负责处理公平策略的内部类FairSync,一个处理非公平策略的内部类NonfairSync,它们两个都是Sync的子类。ReentrantReadWriteLock内部还有两把锁,一个把是读锁ReadLock,一把是写锁WriteLock,它们都是Lock接口的实现类。

1.1 ReentrantReadWriteLock的构造方法

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

可以看到有一个无参构造方法,默认是采用非公平锁策略,还有一个是有参构造方法,根据传入的参数来决定是否采用公平策略。
如果采用公平策略的话,就调用FairSync的无参构造方法:

 static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

FairSync的无参构造方法(默认没有写出来)会调用父类Sync的无参构造方法
Sync的无参构造方法:

 Sync() {
            readHolds = new ThreadLocalHoldCounter();
            setState(getState()); // ensures visibility of readHolds
        }

Sync的无参构造方法主要是做了两件事情,一个是给成员变量readHolds赋值,一个是给state赋值。

如果采用非公平策略的话,就调用NonfairSync的无参构造方法:

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            return apparentlyFirstQueuedIsExclusive();
        }
    }

NonfairSync的无参构造方法(默认没有写出来)同样也会调用父类Sync的无参构造方法。

创造一个ReadLock读锁出来
通过readerLock = new ReadLock(this)来创建一个读锁出来。

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

创造一个WriteLock写锁出来
通过writeLock= new WriteLock(this)来创建一个读锁出来。

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

总结:ReentrantReadWriteLock的构造方法主要是干了一下几件事情

  1. 根据是否采用公平策略:如果采用了公平策略就new一个FairSync类出来并赋值给ReentrantReadWriteLock的成员变量sync,如果采用了非公平策略就new一个NonfairSync类出来并赋值给ReentrantReadWriteLock的成员变量sync。在new NonfairSync类或者new FairSync类的同时也会去调用Sync类的无参构造方法,并将Sync类的成员变量readHolds初始化一下,也将AQS的成员变量state初始化一下。
  2. 创造了一把读锁:通过调用 new ReadLock(this)来创造一把读锁,并将这把读锁赋值给ReentrantReadWriteLock的成员变量readerLock。在new ReadLock类的同时也将ReentrantReadWriteLock的成员变量sync值赋值给了ReadLock的成员变量sync
  3. 创造了一把写锁:通过调用 new WriteLock(this)来创造一把写锁,并将这把写锁赋值给ReentrantReadWriteLock的成员变量writerLock。在new WriteLock类的同时也将ReentrantReadWriteLock的成员变量sync值赋值给了WriteLock的成员变量sync。

1.2 ReentrantReadWriteLock的成员变量的作用及其内部类中成员变量的作用。

ReentrantReadWriteLock主要有三个成员变量:

  /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics */
    final Sync sync;

成员变量readerLock,writerLock主要是用来保存调用ReentrantReadWriteLock无参构造方法时所创造出来的ReadLock实例对象与WriteLock实例对象。sync主要是用来保存调用ReentrantReadWriteLock无参构造方法时所创造出来的FairSync对象或者是NonfairSync对象。

内部类FairSync和NonfairSync没有自己的成员变量

内部类Sync的主要成员变量:

        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;
        private transient ThreadLocalHoldCounter readHolds;
        private transient HoldCounter cachedHoldCounter;
        private transient Thread firstReader = null;
        private transient int firstReaderHoldCount;

firstReader的作用是记录第一个获取到读锁的是哪一个线程。因为第一个获取到读锁的线程后面可能还会再重复获取这把读锁,所以firstReaderHoldCount的作用是记录第一个获取到读锁的这个线程,获取这把读锁的次数,也就是重复获取了几次。

cachedHoldCounter的作用是记录最后获取读锁的是哪一个线程。同样我们也需要记录最后获取读锁的这个线程,获取这把读锁的次数。所以cachedHoldCounter变量的类型是一个HoldCounter类型。
Sync的内部类HoldCounter的作用是:记录一下获取读锁的是哪一个线程(tid线程id来记录)以及这个线程获取了这把读锁几次(count来记录)

static final class HoldCounter {
            int count = 0;
            // Use id, not reference, to avoid garbage retention
            final long tid = getThreadId(Thread.currentThread());
        }

readHolds变量: 我们知道ReentrantReadWriteLock是运行在一个多线程的场景下的。当一个线程获取了读锁,运行着,期间还有其他的线程也获取了读锁,最后这个线程还想再获取一次读锁,那么我们需要将这个线程第一次获取读锁时实例化的HoldCounter对象找到并将count+1。那么我们怎么找到第一次获取读锁时实例化的HoldCounte对象。如果要将一个实例化对象跟一个线程绑定在一起,那我们常用到的是ThreadLocal。而readHolds变量的类型就是一个ThreadLocal类型。readHolds变量的类型是ThreadLocalHoldCounter类,而ThreadLocalHoldCounter类是ThreadLocal的子类。
readHolds变量的作用是当一个线程获取到了读锁,我们需要将这个线程id跟这个线程获取这个读锁几次了这两种信息封装成HoldCounter对象保存下来,以供后续使用。那我们就用到了readHolds变量,readHolds变量是一个ThreadLocalHoldCounter类型。当我们调用readHolds.get()方法时,我们会取到存储在当前线程中的HoldCounter对象,如果没有,那么就新建一个HoldCounter对象放进去。那么当ReentrantReadWriteLock的线程上下文发生了几次切换后,最后又回到了这个线程,我们依然能够拿的到之前新建的HoldCounter对象。
总结来说,每当一个线程获取了读锁,就调用readHolds.get()方法将这个线程的线程id跟获取这把读锁的次数信息保存在这个线程中,以供后续拿的到。

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

问题:为什么要用firstReader来记录第一个获取读锁的线程,cachedHoldCounter来记录最后获取读锁的线程?
主要是为了效率考虑的,因为获取、释放读锁的线程往往都是最近获取读锁的那个线程,通过下面的代码可以知道,有了上面的那些变量之后,就不用了再去使用readHolds变量来进行查找了,加快了代码执行速度。相当于用空间换时间

1.3 ReentrantReadWriteLock有两把锁,读锁跟写锁,但是只有一个state变量,那么怎么用一个state变量来同时代表两把锁呢?

我们知道state变量是一个int类型,int类型是4个字节一共16位。那么ReentrantReadWriteLock的做法是state变量的高8位用来表示读锁,低8位用来表示写锁。在进行读锁,写锁是state值改变的时候,分别通过位运算符来操作state变量的高8位与低8位就行了。

        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;

        /** Returns the number of shared holds represented in count  */
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** Returns the number of exclusive holds represented in count  */
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

以上代码展示了ReentrantReadWriteLock怎么利用位运算符来操作state变量的高8位与低8位的。这里是java的基础知识点,就不展开讲解了。

2. 读锁ReadLock源码

通过调用reentrantReadWriteLock.readLock()可以获取一把读锁

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

2.1 获取读锁

ReentrantReadWriteLock读锁的获取流程大致如下:

调用ReaderLock类的的lock()方法—>调用AQS的acquireShared(1)—>调用Sync类的tryAcquireShared(1)—>如果tryAcquireShared获取锁失败,返回值小于0,则调用AQS的doAcquireShared(1)方法,将当前线程加入到同步队列中阻塞住

获取读锁的入口方法是:reentrantReadWriteLock.readLock().lock()

//此lock方法是ReadLock类里面的
public void lock() {
            sync.acquireShared(1);
        }

再调用AQS的acquireShared方法

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

如果tryAcquireShared方法的返回值小于0,代表获取读锁失败,则就调用AQS的doAcquireShared方法将当前线程加入到同步队列中并且阻塞住(doAcquireShared方法的源码可以参考AQS源码篇)
而tryAcquireShared方法在AQS中是没有实现的,由ReentrantReadWriteLock中继承了AQS类的内部类Sync提供了实现。

2.1.1 tryAcquireShared方法源码

protected final int tryAcquireShared(int unused) {
             //获取当前线程
            Thread current = Thread.currentThread();
            //将state值赋值给变量c
            int c = getState();
            //判断写锁有没有被获取,如果被获取了的话,判断当前线程跟获取写锁的线程是不是
            //同一个,如果不是,直接返回-1,获取读锁失败。
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            //获取已经有多少个线程持有了这把读锁
            int r = sharedCount(c);
            //如果读不应该阻塞,并且持有这把读锁的线程数量小于65535并且通过CAS操作
            //将state的值成功修改了。代表成功获取了读锁
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    //r等于0表示之前没有线程持有读锁,则将firstReader,firstReaderHoldCount赋值
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    //可重入的情况
                    //代表当前线程跟第一个获取到读锁的线程是同一个
                    //则只需要将firstReaderHoldCount加1,代表这把读锁又被这个线程获取了一次
                    firstReaderHoldCount++;
                } else {
                    //否则,这把读锁已经被其他线程持有了并且当前线程跟第一个获取读锁的线程不是同一个
                    //那么先拿到最后一个获取读锁的线程
                    HoldCounter rh = cachedHoldCounter;
                    //如果rh为null,代表最后获取读锁的线程为null,也就是说当前线程是第二个获取到
                    //这把读锁的线程,那么通过readHolds.get()来初始化一个HoldCounter实例对象
                    //并将此实例对象赋值给cachedHoldCounter引用,成为最后获取读锁的那个线程。
                    //如果rh不为null,但是当前线程跟最后获取读锁的线程不是同一个
                    //那么同样通过readHolds.get()来初始化一个HoldCounter实例对象,赋值给cachedHoldCounter
                    //让当前线程成为最后获取读锁的那个线程。
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    //这一步,表示当前线程就是最后那个获取读锁的那个线程
                    //但是HoldCounter实例对象的count值为0
                    //代表着当前线程在获取这把读锁之前,已经获取了一次或者多次读锁并且还是最后
                    //获取读锁的那个线程,但是,之前获取的读锁都已经释放了,调用了readHolds.remove()方法同时count也变成了0,那么我就不用再重新调用readHolds.get()来新建一个
                    //HoldCounter对象了,直接还用这个对象就好了,提高效率。
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    //count加一,代表这个线程持有这把读锁的次数加一
                    rh.count++;
                }
                //以上的逻辑也体现出增加firstReader,cachedHoldCounter这几个变量确实会提高代码执行效率
                //返回1,代表获取读锁成功
                return 1;
            }
            //用来出来上面没有考虑到的锁重入的情况,以及CAS操作失败的情况
            return fullTryAcquireShared(current);
        }

tryAcquireShared方法的大致流程如下:

  1. 先将当前线程赋值给变量current,再将当前state的值赋值给变量c。

  2. 通过调用exclusiveCount©的方法来判断有没有线程持有了写锁,我们知道state变量的高8位代表了读锁,低8位代表了写锁。exclusiveCount方法就是取得state变量的低8位值,如果为0,代表写锁没有被其他的线程持有。此if条件跳过,接着执行下面的代码。如果不等于0,那就说明大于0,因为不可能存在小于0的情况,说明写锁已经被其他的持有了,根据读写互斥原则,那么应该要立马返回-1吗?其实不是这样的,我们还要判断一下当前线程跟持有写锁的线程是不是同一个,如果不是才返回-1。如果是的话,此if条件跳过,接着执行下面的代码。因为ReentrantReadWriteLock中还要一条原则就是一个线程在持有写锁的过程中,还可以持有读锁,目的也还是提高代码执行效率(也有的文章叫锁降级,也就是写锁变成了读锁,但是我认为这种说法不太准确)。我这个线程本身持有了写锁,如果我再获取读锁,还得先释放写锁,再去竞争读锁,这样是没有必要的。因为加锁的目的就是为了防止在多线程环境下,读写某个共享资源时出现错误,所以才规定读的时候不能写,写的时候不能读,但是同一个线程了,我先写,再读我自己写的内容是不会出现错误的。

  3. 再调用sharedCount©方法取得state变量的高8位的值,赋值给r,代表着持有读锁的线程数量。

  4. 调用readerShouldBlock来判断读是否应该阻塞,此逻辑下面详解。如果读不应该阻塞,再来判断r的值是否超过了65535,因为,8位最大只能存储65535,超过了就不能存储了。如果没有超过,那就采用CAS的操作,来将state的高8位值加1.如果CAS操作成功,就代表获取了这把读锁。

  5. 如果获取了这把读锁,再来判断r的值,如果r值为0表示之前没有线程持有读锁,则将firstReader,firstReaderHoldCount赋值。否则,当前线程跟第一个获取到读锁的线程是不是同一个,如果是同一个,就将firstReaderHoldCount值加1,表示当前线程又再一次获取了这把读锁,这是锁可重入的情况
    如果不是同一个,那么先拿到最后一个获取读锁的线程,赋值给rh。

  6. 果rh为null,代表最后获取读锁的线程为null,也就是说当前线程是第二个获取到这把读锁的线程,那么通过readHolds.get()来初始化一个HoldCounter实例对象并将此实例对象赋值给cachedHoldCounter引用,让当前线程成为最后获取读锁的那个线程。如果rh不为null,但是当前线程跟最后获取读锁的线程不是同一个,那么同样通过readHolds.get()来初始化一个HoldCounter实例对象,赋值给cachedHoldCounter,让当前线程成为最后获取读锁的那个线程。

  7. 如果当前线程就是最后那个获取读锁的那个线程但是HoldCounter实例对象的count值为0,代表着当前线程在获取这把读锁之前,已经获取了一次或者多次读锁并且还是最后获取读锁的那个线程,但是,之前获取的读锁都已经释放了,调用了readHolds.remove()方法同时count也变成了0,那么我就不用再重新调用readHolds.get()来新建一个HoldCounter对象了,直接还用这个对象就好了,提高效率。(有兴趣的读者可以去了解一下ThreadLocal的实现原理就可以发现ThreadLocal效率不高的原因)。

  8. count加一,代表这个线程持有这把读锁的次数加一,然后返回1

  9. 如果readerShouldBlock方法返回true,代表读应该阻塞的情况或者上面的CAS操作失败了,那我我们应该调用fullTryAcquireShared方法再次判断读锁是否应该获取。

2.1.2 readerShouldBlock()方法的作用

首先为什么要有一个readerShouldBlock()。主要是考虑这样的情况,假如一个线程要获取一把写锁,但是发现已经有其他的线程获取了读锁,那我它就在同步队列中等待,此时又来了一些要获取读锁的线程,因为读读是运行存在的。如果,这些后来的要获取读锁的线程可以接着获取读锁的话,那我这个写锁的线程就要继续等待了。那么在一些读操作很多,很高的场景下,一个读操作到来,不管之前有没有获取写锁的线程在等着了,它都直接获取读锁,然后又有很多这样的读操作到来,又获取了读锁,那么这个获取写锁的进程岂不是一直获取不到写锁,一直无限等待下去了。
所以readerShouldBlock()方法的作用就是一个线程如果要想获取读锁,先判断同步队列中已经有没有获取写锁的进程在等着了,如果有的话,返回true,它就不能获取这把读锁(排除可重入性这种情况),即使之前已经有线程获取了读锁,它也不能获取,给获取写锁的线程机会,让它能够获取到写锁。

那么为什么当readerShouldBlock方法返回true了,还要执行fullTryAcquireShared(current)方法呢?
主要是readerShouldBlock方法返回true表示已经有获取写锁的线程在同步队列中等着了,理论上当前线程不能再获取这把读锁了。但是ReentrantReadWriteLock具有锁可重入性质的,如果当前线程之前已经获取了读锁,这次只不过是它的再次获取,那么你不让它获取,就没有意义了,它都已经获取了,再重复获取读锁这是可以的。所以就再执行一下fullTryAcquireShared方法处理一下锁可重入这种情况的。如果readerShouldBlock方法返回false,那么处理锁可重入的情况,在tryAcquireShared方法中是有处理逻辑的。当然当compareAndSetState(c, c + SHARED_UNIT)的CAS操作执行失败的情况,因为在tryAcquireShared方法中,此CAS操作只执行一遍,并不是在一个for死循环中,有再来一次的机会。也在fullTryAcquireShared方法中处理一下

2.1.3 公平策略下readerShouldBlock()方法源码

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

公平策略下直接调用hasQueuedPredecessors()判断同步队列中是否已经有其他的线程在排队的情况。我不管排队的线程是获取写锁的还是获取读锁的,只要有人排队,我就返回true,获取读锁应该阻塞,体现了公平,不让你插队的特性。hasQueuedPredecessors方法源码分析

2.1.4 非公平策略下readerShouldBlock()方法源码

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

直接调用apparentlyFirstQueuedIsExclusive方法

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

apparentlyFirstQueuedIsExclusive方法是判断同步队列中head节点的后继排队的节点是不是独占模式,如果是的话,就返回true,代表有获取写锁的线程在排队着。否则返回false。
此方法返回true的场景:

  1. 首先head节点不能为null,如果为null,说明同步队列还没初始化呢,不可能节点在排队着了。
  2. head不为null的情况下,再来判断head的后继节点s = h.next,s是不是为null,如果为null,说明同步队列中除了head节点就没有其他的节点了或者此时刚好有一个线程在执行加入队列了操作,但是只执行了addWaiter方法中的compareAndSetTail(pred, node)这一段代码之前的代码,还没来的及执行pred.next = node这一部分代码。此时h.next值也是为null,对于这种情况,我们就不管了,就算你是一个获取写锁的线程,我们也不管,只不过让这个线程获取到写锁的时刻往后推迟了一点而已,因为又有一个新的线程获取了读锁,它又要多等待一会。
  3. 为s不为null的话,那就判断s所代表的线程节点Node是不是独占模式(因为写锁是独占锁),如果不是话,就返回false了。
  4. 如果是独占模式的话,那就判断s.thread值是不是为null,如果不是的话,就返回true,是的话,就返回false。s.thread为null的情况是,当执行h = head时,同步队列中head的下一个节点就是独占模式的节点,但是当代码执行到s.thread!=null之前,这个独占模式的节点可以被其他线程唤醒了,获取了写锁并且还将直接设置成了新的head头节点,执行了setHead方法,将thread变成了null。在这种情况下,这个节点已经成为新的head节点了,那就不予考虑,直接返回false。

2.1.5 fullTryAcquireShared方法的源码

fullTryAcquireShared方法我们上面提到了主要是为了处理当readerShouldBlock方法返回true,表示当前线程不应该获取这把读锁了,但是得要考虑一下如果当前线程之前已经获取了读锁,这一次要再次获取,那么当前线程应该是可以获取这把读锁的,这体现了锁的可重入性。以及处理在tryAcquireShared方法中CAS操作失败的情况,因为在tryAcquireShared方法中CAS操作只执行了一次,不太可靠。

final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            //一个死循环
            for (;;) {
                //将state值赋值给变量c
                int c = getState();
                  //判断写锁有没有被获取,如果被获取了的话,判断当前线程跟获取写锁的线程是不是
            //同一个,如果不是,直接返回-1,获取读锁失败。
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                 
                } 
                else if (readerShouldBlock()) {
                  //在获取读锁应该阻塞的情况下。这一步处理锁可重入的情况

                   //如果当前线程是第一个获取读锁的线程,接着执行后面的代码,而不是返回-1
                   //体现了锁可重入的情况
                    if (firstReader == current) {
                    
                    } else {
                       //如果当前线程不是第一个获取读锁的线程
                       //rh变量代表最后获取读锁的线程,如果rh没有初始化。那就初始化一下
                        if (rh == null) {
                            //将最后获取读锁的线程赋值给rh
                            rh = cachedHoldCounter;
                            
                            //如果rh为null,代表最后获取读锁的线程为null,也就是说当前线程是第二个获取到
                    //这把读锁的线程,那么通过readHolds.get()来初始化一个HoldCounter实例对象
                    //并赋值给rh
                    //如果rh不为null,但是当前线程跟最后获取读锁的线程不是同一个
                    //那么同样通过readHolds.get()来初始化一个HoldCounter实例对象,赋值给rh
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                //如果rh的count为0,代表此线程之前没有获取到过读锁
                                //那么在获取读锁应该阻塞的情况下,应该要返回-1
                                //所以就将之前调用readHolds.get()新建出来的HoldCounter实例对象再重新remove掉
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        //在获取读锁应该阻塞的情况下,rh的count为0,代表此线程之前没有获取到过读锁,不存在锁可重入的情况下,直接返回-1
                        if (rh.count == 0)
                            return -1;
                    }
                }
                //走到这一步说明是执行tryAcquireShared的CAS失败了,来调用fullTryAcquireShared方法的。
                //而不是readerShouldBlock方法返回true才调用fullTryAcquireShared方法的。
                //获取已经有多少个线程持有了这把读锁
                //如果已经获取这把读锁的线程达到了65535个
                //超过了state高8位所表示的最大值,那么再想要获取的话,直接抛异常。
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                 //通过CAS的操作,将state的高8位的值加1,如果失败了,for死循环接着再执行此CAS操作。
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    //如果之前没有线程获取这把读锁,就将firstReader,firstReaderHoldCount变量赋值
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                       //如果当前获取读锁的线程跟第一个获取读锁的线程是同一个就将firstReaderHoldCount加一,体现了锁的可重入性。
                        firstReaderHoldCount++;
                    } else {
                        //如果当前线程不是第一个获取读锁的线程
                       //rh变量代表最后获取读锁的线程,如果rh没有初始化。那就初始化一下
                        if (rh == null)
                            rh = cachedHoldCounter;
                         //如果rh为null,代表最后获取读锁的线程为null,也就是说当前线程是第二个获取到
                    //这把读锁的线程,那么通过readHolds.get()来初始化一个HoldCounter实例对象
                    //并赋值给rh
                    //如果rh不为null,但是当前线程跟最后获取读锁的线程不是同一个
                    //那么同样通过readHolds.get()来初始化一个HoldCounter实例对象,赋值给rh
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                            //这一步,表示当前线程就是最后那个获取读锁的那个线程
                    //但是HoldCounter实例对象的count值为0
                    //代表着当前线程在获取这把读锁之前,已经获取了一次或者多次读锁并且还是最后
                    //获取读锁的那个线程,但是,之前获取的读锁都已经释放了,调用了readHolds.remove()方法同时count也变成了0,那么我就不用再重新调用readHolds.get()来新建一个
                    //HoldCounter对象了,直接还用这个对象就好了,提高效率。
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        //count加一,代表这个线程持有这把读锁的次数加一
                        rh.count++;
                        //将最后一个获取读锁的线程赋值给cachedHoldCounter
                        cachedHoldCounter = rh; // cache for release
                    }
                    //以上的逻辑也体现出增加firstReader,cachedHoldCounter这几个变量确实会提高代码执行效率
                    //返回1,代表获取到了这把读锁。
                    return 1;
                }
            }
        }

fullTryAcquireShared方法大致流程是:

  1. 开启一个死循环
  2. 将state值赋值给变量c
  3. 判断写锁有没有被获取,如果被获取了的话,判断当前线程跟获取写锁的线程是不是同一个,如果不是,直接返回-1,获取读锁失败。
  4. 如果写锁没有被获取,那么判断获取读锁是否应该阻塞。
  5. 如果获取读锁应该阻塞,那么处理锁可重入的情况
  6. 如果当前线程是第一个获取读锁的线程,接着执行后面的代码,而不是返回-1,体现了锁可重入的情况。
  7. 如果当前线程不是第一个获取读锁的线程,rh变量代表最后获取读锁的线程,如果rh没有初始化。那就初始化一下。
  8. 如果rh为null,代表最后获取读锁的线程为null,也就是说当前线程是第二个获取到这把读锁的线程,那么通过readHolds.get()来初始化一个HoldCounter实例对象并赋值给rh,如果rh不为null,但是当前线程跟最后获取读锁的线程不是同一个那么同样通过readHolds.get()来初始化一个HoldCounter实例对象,赋值给rh。
  9. 如果rh的count为0,代表此线程之前没有获取到过读锁,那么在获取读锁应该阻塞的情况下,应该要返回-1,所以就将之前调用readHolds.get()新建出来的HoldCounter实例对象再重新remove掉。
  10. 在获取读锁应该阻塞的情况下,rh的count为0,代表此线程之前没有获取到过读锁,不存在锁可重入的情况下,直接返回-1。
  11. 如果获取读锁不应该阻塞,走到这一步说明是执行tryAcquireShared的CAS失败了,来调用fullTryAcquireShared方法的,而不是readerShouldBlock方法返回true才调用fullTryAcquireShared方法的。
  12. 获取已经有多少个线程持有了这把读锁,如果已经获取这把读锁的线程达到了65535个超过了state高8位所表示的最大值,那么再想要获取的话,直接抛异常。
  13. 通过CAS的操作,将state的高8位的值加1,如果失败了,for死循环接着再执行此CAS操作。
  14. 如果之前没有线程获取这把读锁,就将firstReader,firstReaderHoldCount变量赋值。
  15. 如果当前获取读锁的线程跟第一个获取读锁的线程是同一个就将firstReaderHoldCount加一,体现了锁的可重入性。
  16. 如果当前线程不是第一个获取读锁的线程,rh变量代表最后获取读锁的线程,如果rh没有初始化。那就初始化一下。
  17. 如果rh为null,代表最后获取读锁的线程为null,也就是说当前线程是第二个获取到这把读锁的线程,那么通过readHolds.get()来初始化一个HoldCounter实例对象并赋值给rh。如果rh不为null,但是当前线程跟最后获取读锁的线程不是同一个那么同样通过readHolds.get()来初始化一个HoldCounter实例对象,赋值给rh。
  18. 这一步,表示当前线程就是最后那个获取读锁的那个线程,但是HoldCounter实例对象的count值为0,代表着当前线程在获取这把读锁之前,已经获取了一次或者多次读锁并且还是最后获取读锁的那个线程,但是,之前获取的读锁都已经释放了,调用了readHolds.remove()方法同时count也变成了0,那么我就不用再重新调用readHolds.get()来新建一个HoldCounter对象了,直接还用这个对象就好了,提高效率。
  19. count加一,代表这个线程持有这把读锁的次数加一,将最后一个获取读锁的线程赋值给cachedHoldCounter。
  20. 返回1,代表获取到了这把读锁。

2.2 释放读锁

ReentrantReadWriteLock释放读锁流程大致如下:

调用ReaderLock类的的unlock()方法—>调用AQS的releaseShared(1)—>调用Sync类的tryReleaseShared(1)—>如果tryReleaseShared释放锁成功,返回true,则调用AQS的doReleaseShared()方法,唤醒同步队列中head节点的后继节点。

获取读锁的入口方法是:reentrantReadWriteLock.readLock().unlock()

此unlock方法是ReadLock类里面的
public void unlock() {
            sync.releaseShared(1);
        }

再调用AQS的releaseShared方法

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

如果tryReleaseShared方法的返回值为true,代表释放读锁成功,则就调用AQS的doReleaseShared方法将同步队列head节点的后继节点唤醒(doReleaseShared方法的源码可以参考AQS源码篇)
而tryReleaseShared方法在AQS中是没有实现的,由ReentrantReadWriteLock中继承了AQS类的内部类Sync提供了实现。

2.2.1 tryReleaseShared方法源码

protected final boolean tryReleaseShared(int unused) {
            //取得当前线程
            Thread current = Thread.currentThread();
            //如果当前线程就是第一个获取到读锁的线程
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                //如果第一个获取到读锁的线程只持有了一次这把读锁,
                //那么就将firstReader赋值为null
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                //如果第一个获取到读锁的线程,还重复获取了这把读锁,那么就将获取的数量减一
                //此时不用将firstReader赋值为null,因为此线程还继续的持有这把读锁
                    firstReaderHoldCount--;
            } else {
               //将最后一次获取到读锁的线程赋值给rh
                HoldCounter rh = cachedHoldCounter;
                //如果rh为null,说明,当前线程并没有持有这把读锁,会导致后面代码抛出unmatchedUnlockException异常。
                //因为走到这一步说明,当前线程既不是第一个获取到读锁的线程,
                //而cachedHoldCounter为null,说明了这把读锁除了被第一个获取这把读锁的线程持有。
                //没有被其他的线程所持有,如果有其他的线程所持有,那么cachedHoldCounter是不可能为null的
                //如果rh不为null,但是当前线程跟最后一个获取读锁的线程不是同一个
                //那么就调用readHolds.get()获取HoldCounter实例对象。
                //注意:如果当前线程之前没有获取过读锁,那么readHolds.get()返回的是新建的HoldCounter实例对象,它的count值为0
                //如果当前线程之前获取过读锁,那么readHolds.get()返回的是之前已经创建好的HoldCounter实例对象,它的count值大于0
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                //如果count值小于等于1,那说明,要么当前线程只获取了这把读锁一次,没有发生锁重入的情况。
                //要么就是当前线程没有获取过读锁,count值为0
                //以上两种情况都应该将与当前线程所关联的HoldCounter实例对象,从当前线程中移除出去。
                if (count <= 1) {
                    readHolds.remove();
                    //如果当前线程没有获取过读锁,count值为0
                    //那么当前线程没有资格去释放锁,直接抛出异常
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                //走到这一步,说明当前线程之前获取到了锁,那么就将count减一
                --rh.count;
            }
            for (;;) {
                int c = getState();
                //将state高8位表示的获取读锁的线程数量减一,并通过CAS操作赋值给state变量
                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.
                    //如果没有线程获取读锁了,就返回true,否则返回false。
                    return nextc == 0;
            }
        }

tryReleaseShared方法的大致流程:

  1. 取得当前线程。
  2. 如果当前线程就是第一个获取到读锁的线程。 如果第一个获取到读锁的线程只持有了一次这把读锁,那么就将firstReader赋值为null。如果第一个获取到读锁的线程,还重复获取了这把读锁,那么就将获取的数量减一,此时不用将firstReader赋值为null,因为此线程还继续的持有这把读锁。
  3. 如果当前线程不是第一个获取到读锁的线程。将最后一次获取到读锁的线程赋值给rh。
  4. 如果rh为null,说明,当前线程并没有持有这把读锁,会导致后面代码抛出unmatchedUnlockException异常。因为走到这一步说明,当前线程既不是第一个获取到读锁的线程,而cachedHoldCounter为null,说明了这把读锁除了被第一个获取这把读锁的线程持有。没有被其他的线程所持有,如果有其他的线程所持有,那么cachedHoldCounter是不可能为null的。
  5. 如果rh不为null,但是当前线程跟最后一个获取读锁的线程不是同一个。那么就调用readHolds.get()获取HoldCounter实例对象。注意:如果当前线程之前没有获取过读锁,那么readHolds.get()返回的是新建的HoldCounter实例对象,它的count值为0,如果当前线程之前获取过读锁,那么readHolds.get()返回的是之前已经创建好的HoldCounter实例对象,它的count值大于0。
  6. 如果count值小于等于1,那说明,要么当前线程只获取了这把读锁一次,没有发生锁重入的情况。
    要么就是当前线程没有获取过读锁,count值为0。以上两种情况都应该将与当前线程所关联的HoldCounter实例对象,从当前线程中移除出去。
  7. 如果当前线程没有获取过读锁,count值为0,那么当前线程没有资格去释放锁,直接抛出异常。
  8. 走到这一步,说明当前线程之前获取到了锁,那么就将count减一。
  9. 进行for死循环
  10. 将state高8位表示的获取读锁的线程数量减一并赋值给nextc变量,并将nextc值通过CAS操作赋值给state变量,如果CAS操作成功,那么判断nextc是否为0,为0,代表没有线程获取读锁了,就返回true,否则就返回false。

3. 写锁WriteLock源码

通过调用reentrantReadWriteLock.writeLock()可以获取一把写锁

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

3.1 获取写锁

ReentrantReadWriteLock写锁的获取流程大致如下:

调用WriteLock类的的lock()方法—>调用AQS的acquire(1)—>调用Sync类的tryAcquire(1)—>如果tryAcquire获取锁失败,返回值为false,则调用AQS的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法,将当前线程加入到同步队列中阻塞住

获取读锁的入口方法是:reentrantReadWriteLock.writeLock().lock()

//此lock方法是WriteLock类里面的
 public void lock() {
            sync.acquire(1);
        }

再调用AQS的acquire方法

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

如果tryAcquire方法的返回值为false,代表获取写锁失败,则就调用AQS的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法将当前线程加入到同步队列中并且阻塞住(acquireQueued方法的源码可以参考AQS源码篇)
而tryAcquire方法在AQS中是没有实现的,由ReentrantReadWriteLock中继承了AQS类的内部类Sync提供了实现。

3.1.1 tryAcquire方法源码

 protected final boolean tryAcquire(int acquires) {
           //获取当前线程
            Thread current = Thread.currentThread();
            //获取state值并赋值给变量c
            int c = getState();
            //获取state值的低8位所代表的有多少个线程获取了这把写锁
            int w = exclusiveCount(c);
            //如果c不等于0,说明有锁被其他线程所持有了,但是还不知道是读锁被持有还是写锁被持有还是都被持有了。
            if (c != 0) {
                // 如果w等于0,说明,没有写锁被其他线程所持有,有读锁被其他线程所持有,那么读写互斥,直接返回false,获取写锁失败。
                //如果w不等于0,说明有写锁被其他线程所持有了,那么判断当前线程跟持有写锁的线程是不是同一个,如果不是,写写互斥,直接返回false,获取写锁失败。
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                 //走到这一步,说明,写锁已经被持有了,但是刚好跟当前线程是同一个
                 //因为ReentrantReadWriteLock具有可锁重入性,所以当前线程还可以再次获取这把写锁
                 //但是因为写锁用state的低8位来存储的,所以得判断一下有没有超过65535,超过了就不能再只有了,存储不下了,直接报异常.
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                //将state值的低8位值加一,并返回true,代表获取写锁成功。
                return true;
            }
            //走到这一步说明读锁,写锁还没有被任何的线程所持有
            //那么判断获取写锁是否应该阻塞。
            //如果应该阻塞,那么直接返回false,代表获取写锁失败。
            //如果不应该阻塞,那么采用CAS的操作将state值加1,如果失败了,那么直接返回false,代表获取写锁失败。
            //如果不应该阻塞,那么采用CAS的操作将state值加1,成功了,直接返回true,代表获取写锁成功。
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

tryAcquire方法大致流程如下:

  1. 获取当前线程赋值给current,获取state值并赋值给变量c,获取state值的低8位的值赋值给变量w,w代表的有多少个线程已经获取了这把写锁。
  2. 如果c不等于0,说明有锁被其他线程所持有了,但是还不知道是读锁被持有还是写锁被持有还是都被持有了。
  3. 在c不等于0情况下,如果w等于0,说明,没有写锁被其他线程所持有,有读锁被其他线程所持有,那么读写互斥,直接返回false,获取写锁失败。如果w不等于0,说明有写锁被其他线程所持有了,那么判断当前线程跟持有写锁的线程是不是同一个,如果不是,写写互斥,直接返回false,获取写锁失败。如果当前线程跟持有写锁的线程是同一个,因为ReentrantReadWriteLock具有可锁重入性,所以当前线程还可以再次获取这把写锁。但是因为写锁用state的低8位来存储的,所以得判断一下有没有超过65535,超过了就不能再只有了,存储不下了,直接报异常。没有超过,将state值的低8位值加一,并返回true,代表获取写锁成功。
  4. 如果c等于0,说明读锁,写锁还没有被任何的线程所持有。那么判断获取写锁是否应该阻塞,如果应该阻塞,那么直接返回false,代表获取写锁失败。如果不应该阻塞,那么采用CAS的操作将state值加1,如果失败了,那么直接返回false,代表获取写锁失败。如果CAS的操作成功了,直接返回true,代表获取写锁成功。

3.1.2 writerShouldBlock()方法的作用

writerShouldBlock方法主要是用来体现写锁的公平策略的。

3.1.3 公平策略下writerShouldBlock()方法源码

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

公平策略下直接调用hasQueuedPredecessors()判断同步队列中是否已经有其他的线程在排队的情况。我不管排队的线程是获取写锁的还是获取读锁的,只要有人排队,我就返回true,获取写锁应该阻塞,体现了公平,不让你插队的特性。hasQueuedPredecessors方法源码分析

3.1.4 非公平策略下writerShouldBlock()方法源码

 final boolean writerShouldBlock() {
            return false; // writers can always barge
        }

非公平策略下writerShouldBlock()方法直接返回false,获取写锁不应该阻塞,谁抢到的就是谁的,不管你的先来后到,体现了不公平。

3.2 释放写锁

ReentrantReadWriteLock释放写锁流程大致如下:

调用WriteLock类的的unlock()方法—>调用AQS的release(1)—>调用Sync类的tryRelease(1)—>如果tryRelease释放锁成功,返回true,则调用AQS的unparkSuccessor(h)方法,唤醒同步队列中head节点的后继节点。

获取读锁的入口方法是:reentrantReadWriteLock.writeLock().unlock()

此unlock方法是WriteLock类里面的
public void unlock() {
            sync.release(1);
        }

再调用AQS的release方法

 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

如果tryRelease方法的返回值为true,代表释放写锁成功,则就调用AQS的 unparkSuccessor(h)方法将同步队列head节点的后继节点唤醒(unparkSuccessor方法的源码可以参考AQS源码篇)
而tryRelease方法在AQS中是没有实现的,由ReentrantReadWriteLock中继承了AQS类的内部类Sync提供了实现。

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

tryRelease方法的大致流程如下:

  1. 先调用isHeldExclusively方法判断当前线程跟获取了这把写锁的线程是不是同一个,如果不是,当前线程没有资格释放写锁,抛出异常。
  2. 如果是,将state值减1并赋值给nextc变量。
  3. 再来调用exclusiveCount(nextc)方法,来判断现在还持有这把写锁的线程数量,如果数量为0,那么将state值变成nextc,并返回true,代表释放写锁成功,如果数量不为0,也将state值变成nextc,返回false,代表释放写锁失败。失败的情况,只能是当前线程重复多次持有了这把写锁。只释放一次,还不够,它还接着持有这把写锁。

4. readHolds的get()方法

readHolds.get()方法需要注意的一点是

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

当一个线程第一个获取ReentrantReadWriteLock的读锁时,调用readHolds.get()方法,会走setInitialValue()方法逻辑,setInitialValue()方法会调用initialValue()方法,而这个initialValue()方法在ReentrantReadWriteLock的内部类ThreadLocalHoldCounter中重写了,返回一个HoldCounter实例对象。

 private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
 static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值