导图社区 AbstractQueueSynchronizer
AbstractQueueSynchronizer 源码分析,需要对着源码看,不然不知道在说什么
编辑于2019-06-25 16:44:40AbstractQueueSynchronizer
独占锁
acquire()
tryAcquire()获取锁(子类去实现)
成功
直接运行
失败
addWaiter()
封装当前线程为Node,并添加到Sync queu
Node
结构
Thread thread
表示被放进sync queue的线程
int waitStatus
CANCELD=1
SIGNAL=-1
CONDITION=-2
PROPAGATE=-3
Node prev
CLH双向链表的前驱节点
Node next
CLH双向链表的后继节点
nextWaiter
用于Sync queue的时候,标识该节点是共享的还是独占的
CLH
在AQS中的队列是一个CLH队列,它的head节点永远是一个哑结点(dummy node), 它不代表任何线程(某些情况下可以看做是代表了当前持有锁的线程),因此head所指向的Node的thread属性永远是null。只有从次头节点往后的所有节点才代表了所有等待锁的线程。也就是说,在当前线程没有抢到锁被包装成Node扔到队列中时,即使队列是空的,它也会排在第二个
fast path
node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; }
节点添加都队尾并非是一个原子操作,
设置前驱
设置成队尾
这一步过后,一定能保证设置了前驱,但却不能保证从他的前驱找到这个节点,因为后一步可能没有执行,这也是为什么源码当中很多地方从尾部遍历到头部的原因
讲前驱的next设置成为该节点
enq
自旋直到讲当前线程对应的node添加的Sync queu的队尾
注意一点的是,enq这个方法成功后,返回的是他的前驱节点,当然在addWaiter方法中并没有用到这个返回值
acquireQueued(final Node node, long arg)
自旋
判断当前节点的前驱是否是head,因为如果是head,说不定马上上就可以获取到锁了
tryAcquire(arg)尝试获取锁
成功获取到锁
setHead(),讲当前的节点设置成为头结点
thread=null,将所代表的线程设置为null
pre=null,讲前驱设置为null,因为自己就队列的头了
返回false,表示没有被中断
失败获取到锁
shouldParkAfterFailedAcquire(p, node),当没有失败获取的时候时候是否需要park
当前驱节点的状态是SIGNAL
返回true
shouldParkAfterFailedAcquire(p, node)
LockSupport.park(this),挂起
LockSupport.park(this),醒来后,返回中断状态,用于后续判断是因为中断醒来,还是其他原因醒来
当前驱节点是CANCELD,一直往前找到第一个不是不是CANCELD,并将它设置成为前驱
返回false
如果前驱节点的状态是其他状态(0),则将他设置成为SIGNAl,表示他自己释放锁的时候,记得叫一声它的后继
返回false
如果上面返回true
selfInterrup()
共享锁
acquireShared()
tryAcquireShared(arg)
tryAcquireShared方法说明
如果该值小于0,则代表当前线程获取共享锁失败
如果该值大于0,则代表当前线程获取共享锁成功,并且接下来其他线程尝试获取共享锁的行为很可能成功
如果该值等于0,则代表当前线程获取共享锁成功,但是接下来其他线程尝试获取共享锁的行为会失败
>=0
成功过去到锁,直接执行
<0
doAcquireShared(arg)和获取共享锁的acquireQueued相对应
自旋
判断当前节点的前驱是否是head,因为如果是head,说不定马上上就可以获取到锁了
long r = tryAcquireShared(arg);
r>=0,表示获取到共享锁
setHeadAndPropagate(node, r)
setHead(),讲当前的节点设置成为头结点
thread=null,将所代表的线程设置为null
pre=null,讲前驱设置为null,因为自己就队列的头了
propagate >0 || oldHeader.waitStats <0 || newHader.waitStatus<0
获取下一个节点s=node.next
如果s==null
我的理解是这这里不知道后面即将的是exclusive还是shared,干脆也跟着唤醒就好了
s.isShared
doReleaseShared()
自旋
h != null && h != tail 表示SyncQueue至少又一个在等待
如果head.waitStatus == SIGNAL
CAS成为0,之所以这里要用CAS,因为是共享锁,好多线程都有可能持有锁,也就有可能有多个线程同时释放锁,而且当线程获取到锁的时候,在一定条件下也会唤醒其他线程,而且这个方法本身,如果发现head != h的时候,也会持续唤醒其他线程
不成功
成功则唤醒后继节点
当head.status == 0,那head.status的状态为什么会为0呢,是因为上面设置的吗,如果是的话,那已经进入上面那个分支,逻辑就不会在这里来了。。。那是为什么呢?对,就是因为刚刚才在Sync queque里面加了一个新的线程,有多”刚刚“呢,“刚刚”到他还还没有来得及把他的前驱设置为SINGAL
CAS设置他的前驱h状态为PROPAGATE
失败,失败的原因是人家刚好有设置好了前驱为SIGNAL
成功,表示你你还没有设置成为SINAL,所以我不唤醒你
如果head==h
返回
循环,如果不等于,表示刚刚唤醒的线程已经成功获取到锁,那么这个线程可以帮忙唤醒后面的线程,然后刚刚唤醒的线程也帮忙唤醒后面的线程,这样子会提高吞吐量
条件队列
并发工具的三板斧
状态
一般是一个state属性,它基本是整个工具的核心,通常整个工具都是在设置和修改状态,很多方法的操作都依赖于当前状态是什么。由于状态是全局共享的,一般会被设置成volatile类型,以保证其修改的可见性
队列
队列通常是一个等待的集合,大多数以链表的形式实现。队列采用的是悲观锁的思想,表示当前所等待的资源,状态或者条件短时间内可能无法满足。因此,它会将当前线程包装成某种类型的数据结构,扔到一个等待队列中,当一定条件满足后,再从等待队列中取出
CAS
CAS操作是最轻量的并发处理,通常我们对于状态的修改都会用到CAS操作,因为状态可能被多个线程同时修改,CAS操作保证了同一个时刻,只有一个线程能修改成功,从而保证了线程安全,CAS操作基本是由Unsafe工具类的compareAndSwapXXX来实现的;CAS采用的是乐观锁的思想,因此常常伴随着自旋,如果发现当前无法成功地执行CAS,则不断重试,直到成功为止,自旋的的表现形式通常是一个死循环for(;;)
主题