AQS源码流程
2021-12-26 15:16:25 5 举报
测试
作者其他创作
大纲/内容
逻辑拆分
在进入setHeadAndPropagate()之前会自旋尝试获取锁,直到获取锁成功,进入此方法,更新头节点并唤醒后继节点。进入此方法,因为该线程节点已经获取到了锁,所以先把该线程节点设置成头节点,然后判断现在空闲的信号量如果充足或者节点的后继节点是SIGNAL,那就开始唤醒后继的共享节点。
采用【优化前置】的技巧,开始先尝试是否可以添加节点成功,如果不成功,进入anq()使用自旋的方式添加节点。如果队列为空,没有节点,先创建一个类型为Node的空节点。先把新节点的前驱指针指向队列的尾节点,然后通过CAS将队列尾节点替换成新节点,如果替换成功就再把新节点的前驱节点的后继指针指向新节点;替换失败就继续自旋。
Abstract:抽象的,使用模板方法设计模式。Queue:线程阻塞队列,FIFO,存放等待资源释放信号的线程。Synchronizer:同步器,多线程情况下保证线程安全。CAS+state:在线程安全情况下实现多线程抢锁逻辑。
releaseShared()(释放共享锁)
先通过子类逻辑判断尝试加锁是否成功。加锁成功就直接执行该线程;加锁失败就添加到阻塞队列,然后判断该线程是否需要阻塞。
子类判断释放锁是否成功,如果释放成功,执行下一步方法
acquireQueue()(判断线程是否需要阻塞前搏一搏)
获取锁
setHeadAndPropagate()( 更新头节点并唤醒后继共享节点)
release()(释放互斥锁)
acquire()(获取互斥锁)
先判断如果头节点是SIGNAL状态,就CAS修改为0,表示唤醒成功,如果修改失败就循环获取最新的头节点重新CAS直到成功,CAS成功之后调用唤醒后继节点的方法;如果头节点状态是0,说明现在头节点已经换了,现在手里的头节点不是最新的,那就把手中的头节点设置为PROPAGATE状态,变为这个状态之后,在上一步的setHeadAndPropagate()中获取到这个旧head的线程发现他的状态是<0的那依然会走这个方法继续唤醒后面的节点,使共享节点可以持续唤醒。
子类判断是否获取锁成功,如果不成功,就调用doAcquireShared()在队列新增线程节点 。
当cancelAcquire()要取消的是头节点的后继节点时,执行unparkSuccessor()唤醒后继节点。获取共享锁doAcquireShared()方法发生中断异常时,调用cancelAcquire()取消节点。
调用
子类判断释放锁是否成功,如果释放成功,那就判断头节点的waitStatus如果为SIGNAL就唤醒后继节点,反之,唤醒失败。
同级执行发生中断异常情况下才会执行
AbstractQueueSynchronizer
同级执行
shouldParkAfterFailedAcquire()(判断线程是否需要阻塞)
addWaiter()(线程添加至阻塞队列)
doReleaseShared()( 释放共享节点)
添加进入阻塞队列之后,获取该线程节点的前驱节点是否是头节点,如果是头节点,那就尝试获取锁,如果获取锁成功那就重新设置队列的头结点,并把原头节点的后继指针值为null,便于后续GC回收。如果新节点的前驱节点不是头节点或者获取锁失败,那就判断该线程节点是否需要阻塞。
调用前提:因发生中断异常,调用取消节点方法。取消前的准备工作:1.设置要取消节点的thread属性为null。2.为要取消的节点找一个有效的,状态不为CANCEL的前驱节点3.把有效的前驱节点的后继节点的状态修改为CANCEL。执行取消操作:要取消的节点在队列中的位置有三种:1.队列的尾节点 CAS将尾节点修改为前驱节点。2.队列的中间节点 判断前驱节点为SIGNAL或者(前驱节点有线 程对象并且CAS修改为SIGNAL状态成功), 那就将前驱节点的后继指针指向要取消节点的 后继节点。3.队列中头节点的后继节点 调用unparkSuccessor()唤醒该节点。
释放锁
acquireShared()(获取共享锁)
cancelAcquire()(取消节点)
unparkSuccessor()(唤醒后继节点)
猜想,如果判断是不是需要阻塞,那就是为新节点找到一个标志是SIGNAL(能唤醒后继节点)的前驱节点即可,这样新节点就可以阻塞,等待前驱节点的唤醒。源码:当前节点已经入队了,先看一下当前节点的前驱节点的waitStatus是否为SIGNAL,如果是,就说明这个前驱节点没有问题,直接返回true。如果前驱节点的waitStatus为CANCEL,那说明这个前驱节点可能因为异常或其他原因已经变为无效节点,那就需要在队列中继续往前寻找有效的前驱节点,使用do……while循环从后往前遍历队列直到找到waiteStatus为SIGNAL的节点,就更新新节点的前去指针指向这个有效节点,这个有效节点的后继指针指向新节点。如果前驱节点的waitStatus不是CANCEL也不是SIGNAL,那就使用CAS将状态改为SIGNAL。
首先获取这个节点的waitStatus判断是否为SIGNAL,如果是就使用CAS将这个waitStatus修改值为0,意为当前已经响应了这一次的唤醒操作。判断要释放节点的后继节点如果是CANCEL无效节点,然后在队列从后往前寻找到要释放节点后继的第一个有效节点,找到有效节点后使用LockSupport.unpark()来唤醒这个有效节点。
0 条评论
回复 删除
下一页