ReentrantReadWriteLock实现原理

    xiaoxiao2022-07-05  171

    ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock(); readLock.lock(); sync.acquireShared(1); 1.tryAcquireShared(arg) 1.如果写锁被其他线程持有,获取锁失败, 2.如果读锁不需要阻塞并且读锁的个数在最大值允许范围内,则尝试cas获取锁, 2.1 获取读锁成功,如果读锁次数为1,则将firstReader属性设置成前线程并设置firstReaderHolderCount为1 2.2 如果读锁次数r大于0并且当前线程等于firstReader则更新firstReaderHolderCount值加1 2.3 否则增加总的缓存的读锁次数 2.4操作完成返回1 3.2步骤没成功则执行fullTryAcquireShared().这个方法是TryAcquireShared()的完全版本,就是先尝试性能优化版的方式获取读锁,如果失败在执行完全版本的读锁获取方式 4.获取锁失败则执行下一步骤doAcquireShared(arg) 2.doAcquireShared(arg); 1.addWaiter(Node.SHARED); 1.将当前线程封装成Node 2.如果tail和head未初始化,则初始化,并且都指向node 3.如果tail不为null,则将node插入到同步队列尾部 2.进入自旋. 3.检查当前节点的前置节点是否是head, 1.如果是则尝试tryAcquireShared(arg);方法见上. 2.如果获取锁成功则执行setHeadAndPropagate() 3.setHeadAndPropagate(node, r); 1.将当前节点设置为head节点,由于是重入锁(内部是共享锁)所以需要检查判断是否需要接续后续等待线程获取同步锁 2.如果propagate属性>0或者后续线程在共享锁模式中等待,则执行doReleaseShared(); 3.doReleaseShared(); 1.共享锁模式下的释放操作 2.在head不为null并且head不等于tail的情况下,如果head的waitstatus是Signal(后续节点等待激活状态).则cas设置head的状态为0并且unpark head节点的next节点 3.如果waitstatus为0(初始化状态),则将head的状态设置为PROPAGATE(代表共享等待模式,下一次获取共享锁无条件传播) 4.unpark后续节点线程 4.将原head节点的next指针设置为null(帮助gc 回收) 5.如果interrupted为true则触发selfInterrupt(); 6.将failed设置为false 7.返回; 4.执行shouldParkAfterFailedAcquire(p, node) 1.如果状态已经是SIGNAL则直接返回 2.检查并在同步链中去掉已经CANCELLED的节点 3.将节点设置为SIGNAL状态等待被激活 5.上一步返回true则执行parkAndCheckInterrupt() 1.park当前线程 2.返回线程的interrupted状态 6.如果第五步返回true则将interrupted设置为true 7.在finally块中将failed等于true的节点执行 cancelAcquire(node);.这里唯一可能执行的情况是上面的selfInterrupt()触发的情况; 1.node=null直接返回不做处理 2.node的thread属性设置为null 3.检测node的pre节点并且移除出于CANCELLED状态的节点, 4.将node节点的状态设置为CANCELLED状态 5.如果node是tail节点.则cas更新tail为pre节点,并将pred 的next节点设置为null(因为是tail了不能有next节点) 6.如果不是tail节点也不是head节点,则cas移除同步链中的node节点 7.如果是head则执行unparkSuccessor(node), 8.将node.next设置为自己,帮助gc回收 readLock.unlock(); sync.releaseShared(1); 1.先执行tryReleaseShared(arg) 1.如果firstReader属性等于当前线程 1.如果firstReaderHoldCount==1,则如果firstReader设置为null,因为等于1在释放一次锁之后就不在占有锁了 2.否则执行firstReaderHoldCount--; 2.firstReader不等于当前线程 1.拿到cachedHoldCounter值,这是将threadLocal变量以线程id为key的缓存值 2.如果cachedHoldCounter为空,或者cachedHoldCounter不是当前线程的值,则重新通过readHolds.get()获取. 3.拿到HoldCounter中的count值, 1.如果小于1则移除readHolds中当前现成的值 2.如果count小于0,抛出异常. 4.执行--count操作 3.进入自旋 1.拿到锁状态属性state 2.计算锁的新值,并使用cas更新 3.返回新值==0 代表是否已经释放锁 2.如果第一步返回true则执行doReleaseShared()(说明见上);并返回TRUE 3.返回false ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock(); writeLock.lock() sync.acquire(1); 1.执行tryAcquire(arg) 1.获取state锁状态属性 2.根据state计算出写锁的值(读锁和写锁 使用的是同一个字段state(利用分段高位低位存储不同的标识实现的) 3.如果state不为0 1.如果写锁为0(则读锁肯定不为0).或者当前拥有写锁的线程不是当前线程则返回false 2.如果写锁超过最大值,则抛出异常 3.使用setState更新最新的状态值(因为上面的判断,能走到这一步代表是当前线程重复获取读锁所以不需要使用cas 4.返回true 4.执行writerShouldBlock()判断是否需要挂起写线程, 1.如果是公平锁则通过hasQueuedPredecessors判断是否有前置等待的同步队列 2.如果是非公平锁则直接返回false,代表可以强占锁 5.如果上一步返回true,则尝试cas设置最新的锁状态,失败则返回false 6.获得锁成功,则执行setExclusiveOwnerThread(current);,将当前线程设置为独占锁的拥有线程 7.返回true 2.如果获取锁失败则执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 1.执行addWaiter(Node.EXCLUSIVE)方法,加入同步链 2.failed属性默认true 3.interrupted属性默认false; 4.进入自旋 1.如果当前节点的前置节点是head,则执行tryAcquire(arg)尝试获取所 1.如果成功,将node更新为head,这里不需要同步,因为只有一个线程可以获得锁 2.原head节点的next设置null 3.failed设置为false 4.返回interrupted属性.这里是false; 2.上一步尝试获取锁失败,则执行shouldParkAfterFailedAcquire判断是否需要挂起 3.如果需要挂起则执行parkAndCheckInterrupt() 4.再次被唤醒时如果parkAndCheckInterrupt()返回为true则将interrupted设置为true 5.继续下一轮自旋 5.在finally块中将failed等于true的节点执行 cancelAcquire(node); writeLock.unlock() sync.release(1); 1.执行tryRelease(arg) 1.通过isHeldExclusively判断如果当前线程不是拥有独占所的线程则抛出异常 1.return getExclusiveOwnerThread() == Thread.currentThread(); 2.计算nextc锁释放后的新值(因为是重入锁) 3.通过判断free = exclusiveCount(nextc) == 0来判断当前锁是否已经释放 4.如果已经彻底释放(free为true)则通过setExclusiveOwnerThread将独占锁拥有线程设置为null 5.更新state值为nextc 6.返回free 2.如果完全释放锁,则通过unparkSuccessor唤醒后继节点线程并返回true.如果是重入锁直接返回了.不需要唤醒后继线程 3.返回false,表示只是减少了的锁的数量,没有释放锁
    最新回复(0)