【锁】读写锁--ReentrantReadWriteLock源码分析

10 篇文章 2 订阅
7 篇文章 0 订阅
1.什么读写锁–ReentrantReadWriteLock?

撸源码前,可先看:【锁】公平锁/非公平锁/可重入锁/递归锁/自旋锁/独占锁/共享锁/读写锁

2.开撸

2.1 类图
先从类图开始,一步一步看。

  • idea找到JUC包里面的ReentrantReadWriteLock,右键如图:
    在这里插入图片描述
  • 打开类图:
    在这里插入图片描述
  • 我们可以看到ReentrantLock里面有一个Sync抽象类,四个内部静态类NonfairSync和FairSync,ReadLock和WriteLock.
  • 其中NonfairSync和FairSync是公平锁和非公平锁的实现
  • ReadLock和WriteLock就是读锁和写锁的实现
  • 而Sync我们找的它,右键:
    在这里插入图片描述
  • Sync的类图
    在这里插入图片描述
  • Sync继承于AbstractQueuedSynchronizer
  • 而AbstractQueuedSynchronizer里面维护了一个以Node为节点的AQS队列。
  • AQS队列,就是ReentrantReadWriteLock的核心。

2.2 AQS队列

  • 追根溯源,AQS队列是由AbstractQueuedSynchronizer维护
  • 而AbstractQueuedSynchronizer由继承于AbstractOwnableSynchronizer
  • 我们先看AbstractOwnableSynchronizer源码:
public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {
    
    protected AbstractOwnableSynchronizer() { }
    private transient Thread exclusiveOwnerThread;

    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

  • exclusiveOwnerThread从名字就能看出来独家拥有线程,也就是独占模式锁的拥有者。
  • 而AQS定义两种资源共享方式:

1,Exclusive(独占,只有一个线程能执行,ReentrantLock使用该模式)

2,Share(共享,多个线程可同时执行,Semaphore/CountDownLatch使用该模式)。

  • AQS队列底层其实就是链表,而在AbstractQueuedSynchronizer中链表节点是有内部类Node来充当,在Node里有这么两行代码:
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;


  • exclusiveMarker to indicate a node is waiting in shared mode
    共享模式:CountDownLatch使用该模式,详细可见:【JUC】CountDownLatch源码分析

  • Marker to indicate a node is waiting in exclusive mode
    独占模式:ReentrantLock使用该模式,详细可见:【锁】【JUC】可重入锁/AQS队列–ReentrantLock源码分析

  • 至于ReentrantReadWriteLock则是两者都用,读锁用的是共享锁,写锁用的是独占锁,后面看源码时详细聊。

  • 咱先看一下AbstractQueuedSynchronizer的核心成员:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    private static final long serialVersionUID = 7373984972572414691L;
  
    protected AbstractQueuedSynchronizer() { }

   //队列节点
    static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;
        
        /** waitStatus value to indicate thread has cancelled */ 线程已经被取消,该状态的节点不会再次阻塞。
		static final int CANCELLED =  1;
		/** waitStatus value to indicate successor's thread needs unparking */ 线程需要去被唤醒
		static final int SIGNAL    = -1;
		/** waitStatus value to indicate thread is waiting on condition */ 线程正在唤醒等待条件
		static final int CONDITION = -2;
		/**
		 * waitStatus value to indicate the next acquireShared should //线程的共享锁应该被无条件传播
		 * unconditionally propagate
		 */
		static final int PROPAGATE = -3;
        
        //上面四个值就是下面变量waitStatus的值
        volatile int waitStatus;
        //前一个节点
        volatile Node prev;
        //下一个节点
        volatile Node next;
        //当前节点代表的线程
        volatile Thread thread;
        /**
        *等待节点的后继节点。如果当前节点是共享的,那么这个字段是一个SHARED常量,也就是说节点类型(独占和共享)和
        *等待队列中的后继节点共用一个字段。(注:比如说当前节点A是共享的,那么它的这个字段是shared,也就是说在这个等
        *待队列中,A节点的后继节点也是shared。如果A节点不是共享的,那么它的nextWaiter就不是一个SHARED常量,即是独
        *占的。
        */
        Node nextWaiter;
}
    //头结点
    private transient volatile Node head;
     //尾节点
    private transient volatile Node tail;
    //状态值
    private volatile int state;
  • 如果了解CLH队列的话你会感觉眼熟,因为AQS队列就是它的一个变体,至于CLH队列是什么,看这里:【锁】自旋锁-MCS/CLH队列
  • AbstractQueuedSynchronizer的这些成员中,volatile修饰的state是重点,volatile关键字保证了线程间可见性。具体可见:【JUC】volatile关键字相关整理
  • ReentrantReadWriteLock加锁记录就保存在state中,但因为ReentrantReadWriteLock是读写锁,既要保存是否加锁,还要保存锁的类型,以及重入,只有state一个字段是无法满足的。所以ReentrantReadWriteLock的内部类Sync 对其进行了改造,一方面引入了HoldCounter来保存重入数量;一方面将state分为高低位,state是int类型,32位, 高16位保存共享锁(读锁),低16位保存独占锁(写锁)。
  • 接下来看一下详细实现;
abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 6317671515068378041L;

		//state被拆分为高低位,高16位保存共享锁(读锁),低16位保存独占锁(写锁)
		//这几个字段是为了区分state的高低位的
        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位也就是高16位的值。
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        // 返回独占锁的数量。EXCLUSIVE_MASK转为2进制是16个1,c是32位,进行与计算,高16位结果全为0,低16位结果与c的低16位相同,也就是c的低16位的值。
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

        //HoldCounter类主要用于读锁的可重入,记录了重入的次数:count。
        //HoldCounter会被包装在ThreadLocal中,是线程安全的,并且缓存在cachedHoldCounter变量中
        static final class HoldCounter {
            int count;          // 初始值为0
            // 这里使用id,而不是引用,目的是为了避免垃圾回收
            final long tid = LockSupport.getThreadId(Thread.currentThread());
        }

        //这里重写了initialValue方法,可以保证获取到的HoldCounter对象是同一个,不会重复创建。
        static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }

        /**
         * 当前线程持有的 可重入读锁 的数量。
         * 仅在构造函数和readObject方法中初始化。
         * 当count的值降至0时删除。
        */
        private transient ThreadLocalHoldCounter readHolds;
        
        //缓存最后一个成功获取读锁的线程的HoldCounter 
        //通常来说,下一个要释放锁的线程就是最后一个获取锁的线程。这里使用cachedHoldCounter来单独保存,可以减少在ThreadLocal中查找的开销
        private transient HoldCounter cachedHoldCounter;

        //第一个获取读锁的线程,确切来说firstReader是最后一次将共享计数从0更改为1的唯一线程,并且从那时起就没有释放读锁定; 如果没有这样的线程,则返回null。
        private transient Thread firstReader;
        //第一个获取读锁线程的 hold count
        private transient int firstReaderHoldCount;

        Sync() {
        	//初始化一个readHolds,其count默认是0
            readHolds = new ThreadLocalHoldCounter();
            setState(getState()); // 这一步目的是为了确保state的可见性
        }
}
  • 还有,在多线程并发请求锁时,采用CAS修改state的值,修改成功则获取锁成功,修改失败则加入到AQS等待队列尾部,至于什么是CAS,可见:【JUC】 Java中的CAS
  • 另一个要注意的AbstractQueuedSynchronizer里Node中的状态属性waitStatus,它默认为零,还有四个值见上面Node代码,它的值会被后置结点在获取锁失败后阻塞前修改,用于提醒你在释放锁后去唤醒它,具体详细情况后面源码聊。
  • AQS结构图
    在这里插入图片描述
  • AQS队列的头结点并不关联任何线程,他是一个默认的Node节点。

2.3 开始撸代码

  • 先看构造函数:
 public ReentrantReadWriteLock() {
        this(false);
    }

    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
public void lock() {
    sync.acquire(1);
}
  • 调用AQS类的java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire方法:
public final void acquire(int arg) {
    if(!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • 大概流程如下

1,,acquire(1)方法中首先尝试获取锁tryAcquire(),如果获取失败,则将当前线程以独占模式Node.EXCLUSIVE加入等待队列尾部(addWaiter方法)。

2,acquireQueued():以独占无中断模式获取锁,这个方法会一直无限循环,直到获取到资源或者被中断才返回。如果等待过程中被中断则返回true。这里有自旋锁的意思,加入队列中的线程,不断的重试检测是否可以执行任务。

3,如果以上两个方法都返回true,也就是没获得锁,加入了AQS队列,修改了前一个结点的waitStatus值为-1,最后阻塞自己,等待前置结点唤醒。

  • 接下来一个一个方法撸源码:

tryAcquire
-具体实现在java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryAcquire中:

@ReservedStackAccess
protectedfinal boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();//获取state的值
    int w = exclusiveCount(c);//获取独占锁的数量
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;//走到这里,c不等于0,w(低16位)是0,所以高16位肯定有值。也就是说此时有共享锁存在。
        if (w + exclusiveCount(acquires) > MAX_COUNT)//校验独占锁的数量是否超标
            throw new Error("Maximum lock count exceeded");

        //走到这里,说明当前是写锁重入。因为走到这一步只可能是:c不是0,w不是0,exclusiveOwnerThread标识的线程是当前线程。
        setState(c + acquires);
        return true;
    }
    
    //走到这里说明c的值是0。也就是读锁和写锁都没有。当然可以加锁了。
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);//加锁成功,设置获取独占锁的线程为当前线程
    return true;
}
  • 上面就是获取锁的过程,大概意思:

1,state的值是0,说明当前没有锁,那么判断写锁是否应该阻塞 (后面讲),如果不阻塞,则修改state的值,修改成功则加锁成功

2,state的值不是0,低16位是0,说明高16位不是0,此时有共享锁存在,不可以加锁。

3,state的值不是0,低16位不是0,但是当前获取独占锁的线程不是当前线程,不可以加锁

4,state的值不是0,低16位不是0,当前获取独占锁的线程是当前线程,说明是写锁重入,可以加锁

  • 要注意的是writerShouldBlock方法,它是来判断写锁是否应该被阻塞,公平锁和非公平锁的实现是不同的:
  • 公平锁实现:
static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }
public final boolean hasQueuedPredecessors() {
        Node h, s;
        if ((h = head) != null) {
            if ((s = h.next) == null || s.waitStatus > 0) {
                s = null; // traverse in case of concurrent cancellation
                for (Node p = tail; p != h && p != null; p = p.prev) {
                    if (p.waitStatus <= 0)
                        s = p;
                }
            }
            if (s != null && s.thread != Thread.currentThread())
                return true;
        }
        return false;
    }
  • 非公平锁实现:
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;
    final boolean writerShouldBlock() {
        return false; // writers can always barge
    }
    final boolean readerShouldBlock() {
        return apparentlyFirstQueuedIsExclusive();
    }
}
  • 公平锁会判断head的下一个节点是否为当前请求的线程,如果不是,说明前面有其他线程排队,当前线程应该加入等待队列中。

addWaiter和enq

  • 按照指定模式(独占还是共享)将节点添加到等待队列。
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    //1、首先尝试以快速方式添加到队列末尾
    Node pred = tail;//pred指向现有tail末尾节点
    if (pred != null) {
    //新加入节点的前一个节点是现有AQS队列的tail节点
        node.prev = pred;
      	//CAS原子性的修改tail节点
        if (compareAndSetTail(pred, node)) {    
            //修改成功,新节点成功加入AQS队列,pred节点的next节点指向新的节点
            pred.next = node;
            return node;
        }
    }
    //2、pred为空,或者修改tail节点失败,
    //则走enq方法将节点插入队列
    enq(node);
    return node;
}
private Node enq(final Node node) {
    for(;;) {//CAS
        Node t = tail;
        if (t == null) { 
        // 必须初始化。这里是AQS队列为空的情况。
        //通过CAS的方式创建head节点,并且tail和head都指向
        //同一个节点。
            if (compareAndSetHead(new Node()))
            //注意这里初始化head节点,并不关联任何线程!!
                tail = head;
        } else {
        //这里变更node节点的prev指针,并且移动tail指针指向node,
        //前一个节点的next指向新插入的node
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
  • 上面addWaiter方法的第一行代码new Node(Thread.currentThread(), mode);,会创建一个node对象,该对象的重要属性值初始化为:
nextWaiter = Node.EXCLUSIVE; // Node.EXCLUSIVE值为null
thread = Thread.currentThread();
waitStatus = 0;// 默认是0
  • addWaiter首先会以快速方式将node添加到队尾,如果失败则走enq方法。失败有两种可能,一个是tail为空,也就是AQS为空的情况下。另一是compareAndSetTail失败,也就是多线程并发添加到队尾,此时会出现CAS失败。
  • 注意enq方法,在t==null时,首先创建空的头节点,不关联任何的线程,nextWaiter和thread变量都是null。

acquireQueued

  • tryAcquire失败没有获取到锁,addWaiter加入了AQS等待队列,进入acquireQueued方法中,acquireQueued方法以独占无中断模式获取锁,这个方法会一直无限循环,直到获取到资源或者被中断才返回。
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;//是否获取到资源
    try {
        boolean interrupted = false;//是否中断
        for (;;) {
            //获取前一个节点
            final Node p = node.predecessor();

			//如果当前node节点是第二个节点,紧跟在head后面,
			//那么tryAcquire尝试获取资源
            if (p == head && tryAcquire(arg)) {                
                setHead(node);//获取锁成功,当前节点成为head节点
                p.next = null; // 目的:辅助GC
                failed = false;
                return interrupted;//返回是否中断过
            }
            
            //当shouldParkAfterFailedAcquire返回成功,
            //也就是前驱节点是Node.SIGNAL状态时,
            //进行真正的park将当前线程挂起,并且检查中断标记,
            //如果是已经中断,则设置interrupted =true。
            //如果shouldParkAfterFailedAcquire返回false,
            //则重复上述过程,直到获取到资源或者被park。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);//添加AQS失败,取消任务
    }
}

//前面讲过,head节点不与任何线程关联,他的thread是null,
//当然head节点的prev肯定也是null
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

//在Acquire失败后,是否要park中断
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws= pred.waitStatus;//获取到上一个节点的waitStatus
    if (ws == Node.SIGNAL)//前面讲到当一个节点状态时SIGNAL时,
    //他有责任唤醒后面的节点。所以这里判断前驱节点是SIGNAL状态,
    //则可以安心的park中断了。
        return true;
    if (ws > 0) {
        /*
         * 过滤掉中间cancel状态的节点
         * 前驱节点被取消的情况(线程允许被取消哦)。向前遍历,
         * 直到找到一个waitStatus大于0的(不是取消状态或初始状态)
         * 的节点,该节点设置为当前node的前驱节点。
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * 修改前驱节点的WaitStatus为Node.SIGNAL。
         * 明确前驱节点必须为Node.SIGNAL,当前节点才可以park 
         * 注意,这个CAS也可能会失败,因为前驱节点的WaitStatus状态
         * 可能会发生变化
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

//阻塞当前线程
//park并且检查是否被中断过
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

cancelAcquire

  • acquireQueued方法在出现异常时,会执行cancelAcquire方法取消当前node的acquire操作。
private void cancelAcquire(Node node) {
    if (node == null)
        return;

    node.thread = null;

    // 跳过中间CANCELLED状态的节点
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    Node predNext = pred.next;

    // 将node设置为CANCELLED状态
    node.waitStatus = Node.CANCELLED;

    // 如果当前节点是tail节点,则直接移除
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {//如果pred不是head节点并且是SIGNAL 状态,
            //或者可以设置为SIGNAL 状态,
            //那么将pred的next设置为node.next,也就是移除当前节点
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            unparkSuccessor(node);//唤醒node的后继节点
        }

        node.next = node; // help GC
    }
}
private void unparkSuccessor(Node node) {
    //如果waitStatus为负数,则将其设置为0(允许失败)
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //唤醒当前节点后面的节点。通常是紧随的next节点,
    //但是当next被取消或者为空,则从tail到node之间的所有节点,
    //往后往前查找直到找到一个waitStatus <=0的节点,将其唤醒unpark
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}
  • 总结一下:

1、设置thread变量为空,并且设置状态为canceled

2、跳过中间的已经被取消的节点

3、如果当前节点是tail节点,则直接移除。否则:

4、如果其前驱节点不是head节点并且(前驱节点是SIGNAL状态,或者可以被设置为SIGNAL状态),那么将当前节点移除。否则通过LockSupport.unpark()唤醒node的后继节点

2.3.1 写锁的解锁源码分析

  • writeLock.unlock
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方法,他的实现在ReentrantReadWriteLock.Sync类中
protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())//独占锁的持有者不是当前线程,抛异常
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;//这里是独占锁,低16位,所以可以直接相减,下一步会校验是不是低16位减为0
    boolean free = exclusiveCount(nextc) == 0;//判断低16位的独占锁是否都释放完
    if (free)
        setExclusiveOwnerThread(null);//独占锁已经释放完,exclusiveOwnerThread设为null
    setState(nextc);//释放state
    return free;
}
  • 独占锁的释放过程比较简单的,首先检查加锁的是不是当前线程,不是则抛异常。接下来校验释放releases数量的state后,低16位是否为0,是,则说明独占锁释放完成,固设置exclusiveOwnerThread=null,否则设置新的state。

  • 接下来看一下unparkSuccessor方法,写锁释放后,唤醒队列中后继节点,该后继节点可能是读锁也可能是写锁。

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        node.compareAndSetWaitStatus(ws, 0);//如果<0(这里一般是Node.PROPAGATE这种状态),重新将其waitstatus设置为0

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {//如果后继节点被取消,那么找到后继第一个waitStatus <= 0的节点,将其唤醒
        s = null;
        for (Node p = tail; p != node && p != null; p = p.prev)
            if (p.waitStatus <= 0)
                s = p;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}
  • 唤醒AQS队列中的下一个一个等待节点,该节点查找顺序为从AQS队列的尾节点开始查找的,找到最后一个 waitStatus<=0 的那个节点,通过LockSupport.unpark将其唤醒。

2.3.1 读锁的加锁源码分析

  • readLock.lock
public void lock() {
   sync.acquireShared(1);
}
  • 该方法会调用AQS的acquireShared方法
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
  • 首先尝试获取锁tryAcquireShared,如果失败则执行doAcquireShared。

tryAcquireShared

  • tryAcquireShared的实现在ReentrantReadWriteLock中:
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    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) {//共享锁数量为0:则记录最后一次将共享锁计数从0更改为1的线程,初始化firstReaderHoldCount =1
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;//firstReader 标识的第一个读线程 读锁重入
        } else {
            HoldCounter rh = cachedHoldCounter;//cachedHoldCounter:缓存的最后一个获取读锁的线程的HoldCounter 
            if (rh == null || rh.tid != LockSupport.getThreadId(current))//缓存为空,或者缓存的线程id不是当前线程
                cachedHoldCounter = rh = readHolds.get();//获取threadlocal中当前线程的计数器缓存HoldCounter ,并将其添加到缓存cachedHoldCounter中
            else if (rh.count == 0)//由于count为0时,readHolds中的缓存会被移除掉,所以这里要在set一下
                readHolds.set(rh);//将缓存的cachedHoldCountert添加到threadlocal变量readHolds中
            rh.count++;//当前线程重入计数器加一
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

doAcquireShared

  • 先看一下tryAcquireShared方法有哪些情况会返回<0

1,有独占锁存在,并且该独占锁不是当前线程加的。

2,没有独占锁存在,并且读锁需要等待时,并且当前线程不是第一个获取读锁的线程,并且最后一个获取锁的线程全部已经释放锁时。

  • doAcquireShared: 以共享无中断模式获取。无限循环获取读锁,直到成功或者发生异常
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);//以共享模式添加到AQS队列尾部。
    boolean interrupted = false;
    try {
        for (;;) {
            final Node p = node.predecessor();//获取上一个node
            if (p == head) {//上一个节点是head节点,说明轮到自己获取锁了,则尝试获取读锁tryAcquireShared
                //翻阅源码,在ReentrantReadWriteLock中,这个返回值 r 要么是1,要么是-1
                //当然其他情况,可能返回0
                int r = tryAcquireShared(arg);
                if (r >= 0) {//获取读锁成功。
                    setHeadAndPropagate(node, r);
                    p.next = null; // 老的头结点已经没有用了,其next指向null,目的为了GC释放内存
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node))//检测是否应该park等待
                interrupted |= parkAndCheckInterrupt();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    } finally {
        if (interrupted)
            selfInterrupt();//被中断过,重置中断标记
    }
}

//设置头结点并且广播 唤醒后继节点
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head;
    setHead(node);
    
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        
        Node s = node.next;
        if (s == null || s.isShared())//下一个节点如果为空,或者是共享模式的节点
            doReleaseShared();
    }
private void doReleaseShared() {
    for (;;) {
        Node h = head;//注意这里已经是新的头节点了
        if (h != null && h != tail) {//头结点存在并且不等于tail,也就是AQS队列不为空的情况
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {//是SIGNAL状态,则需要unpark后继节点
                if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))//将头结点的waitstatue设置为0,以后就不会再次唤醒后继节点了。这一步是为了解决并发问题,保证只unpark一次!!
                    continue;            // 设置失败则进行重试
                unparkSuccessor(h);//唤醒头节点的后继节点
            }
            else if (ws == 0 && !h.compareAndSetWaitStatus(0, Node.PROPAGATE))//这里尝试将头结点设置为PROPAGATE。保证传播性
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        node.compareAndSetWaitStatus(ws, 0);//如果<0(这里一般是Node.PROPAGATE这种状态),重新将其waitstatus设置为0

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {//如果后继节点被取消,那么找到后继第一个waitStatus <= 0的节点,将其唤醒
        s = null;
        for (Node p = tail; p != node && p != null; p = p.prev)
            if (p.waitStatus <= 0)
                s = p;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

大概过程总结:

1,acquireShared()申请锁,如果成功,则进入临界区继续执行。如果失败,则加入AQS等待队列,挂起(park)等待被唤醒(unpark)。

2,AQS队列中的读锁被唤醒后,重新尝试获取锁,如果成功,则进入临界区执行,并且唤醒后继共享节点,并且会传递下去一次唤醒所有的共享节点,直到队尾或者遇到写锁。

2.3.1 读锁的解锁源码分析

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

//releaseShared调用AQS类中的releaseShared方法
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {//尝试释放锁,ReentrantReadWriteLock中,只有读锁和写锁全部释放才会返回true
        doReleaseShared();//如果释放锁成功后,没有读锁也没有写锁,那么唤醒AQS的后继节点
        return true;
    }
    return false;
}
  • releaseShared方法很简单,先释放锁,在唤醒后继节点。
  • 我们看ReentrantReadWriteLock中tryReleaseShared的实现:
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {//当前线程是第一个获取读锁线程,释放 firstReader 及firstReaderHoldCount 
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
    //当前线程不是第一个读线程,需要从threadlocal中获取HoldCounter ,释放其count
        HoldCounter rh = cachedHoldCounter;
        if (rh == null ||
            rh.tid != LockSupport.getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();//说明这已经是最后一次释放锁了,直接删除即可
            if (count <= 0)
                throw unmatchedUnlockException();//说明锁计数器出现异常
        }
        --rh.count;//释放count的值
    }
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;//读锁标记减去 1 (还记得高16位表示读锁数量吗?)
        if (compareAndSetState(c, nextc))
            //当没有读锁也没有写锁时,返回true
            return nextc == 0;
    }
}
  • tryReleaseShared方法释放读锁占用的资源,释放以后,如果state!=0,说明有读锁或者写锁。那么直接返回。如果state==0,说明此事没有读锁也没有写锁了,需要唤醒AQS队列中等待的节点。
  • doReleaseShared有两个入口:一个是读锁释放资源后,一个是setHeadAndPropagate方法中。doReleaseShared的源码上面已经分析过。这里说一下不同之处:前面是读锁加锁后setHeadAndPropagate内部调用的doReleaseShared,他前面有一个判断s == null || s.isShared(),也就是说只能后继节点是读锁才可以唤醒后继节点。这也很好理解,一个读锁抢占资源成功,那么AQS队列中后面等待的读锁都应该唤醒进入临界区,这才符合读锁(共享锁)的特性。
  • 然而在读锁释放资源时调用doReleaseShared方法时,此时唤醒的有可能是读锁,也可能是写锁,因为我们并不知道AQS队列中第一个节点是读还是写,所以根据唤醒的节点不同走不同的分支。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值