这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战
前言
本文继续讲解SynchronousQueue队列的非公平策略,SynchronousQueue通过两个内部类实现了公平策略和非公平策略的无缓存阻塞队列,每种操作都需要对应的互补操作同时进行才能完成,例如,入队操作必然对应出队操作,在不涉及超时和中断的情况下,必须等待另一个线程进行出队操作,相互匹配才能执行,否则就阻塞等待。
TransferStack
不同于公平策略下的操作,只有一种状态需要注意:
取消操作:match == this;
SNode
SNode基于栈的节点实现,变量与QNode不同,其中match在两个操作匹配上之后可以通过这个变量找到其匹配的节点,节点类型mode在使用上也有所不同,其他参数可参考TransferQueue的QNode
1 | java复制代码 static final class SNode { |
变量部分
1 | php复制代码 // 数据请求操作 如take操作 代表未被匹配上的消费者 |
CAS操作
CAS更新栈顶指针,比较简单
1 | typescript复制代码boolean casHead(SNode h, SNode nh) { |
判断街道是否匹配
判断m对应的节点是否已经被匹配,和FULFILLING进行位与操作,判断m对应的栈节点处于FULFILLING状态,即已经匹配上了,在transfer里与栈顶节点非相同操作时会入栈一个节点,此节点的mode和普通节点不一样,会通过FULFILLING|mode操作更新mode,故这里最低位来区分是保存数据还是请求数据,高位来区分此节点是否是已经找到匹配节点的节点。
1 | arduino复制代码/** Returns true if m has fulfilling bit set. */ |
snode节点
创建或重置SNode节点,如果为空则创建新的SNode节点,不为空则重置节点的mode和next属性。
1 | ini复制代码 static SNode snode(SNode s, Object e, SNode next, int mode) { |
transfer
和公平模式下的TransferQueue.transfer入队和出队操作类似,统一使用一个方法,即实现接口中的transfer方法来完成。不同点在于3个条件分支:
栈为空或栈顶元素操作类型和当前操作类型相同,入栈阻塞等待;
栈顶非匹配互补节点(匹配互补节点:已经和其他节点匹配上了,mode值高位为1),进行匹配操作;
帮助已经匹配的栈顶节点操作;
1 | scss复制代码 @SuppressWarnings("unchecked") |
阻塞等待唤醒(awaitFulfill)
与TransferQueue.awaitFulfill类似,在当前操作同之前操作相同时,未设置操作时间同时未被外部线程中断则需阻塞等待匹配节点唤醒当前阻塞的线程。
1 | scss复制代码 SNode awaitFulfill(SNode s, boolean timed, long nanos) { |
是否需要自旋操作(shouldSpin)
判断是否需要自旋操作,满足下列情况之一即需要自旋:
栈顶节点等于s节点;
栈顶节点为空;
栈顶节点为已和其他节点匹配的节点;
1 | ini复制代码 boolean shouldSpin(SNode s) { |
清理操作(clean)
清理操作,清理栈节点s的关联关系,同时会清理整个栈节点的取消操作节点,无cleanMe节点,比TransferQueue.clean操作要简单许多
1 | ini复制代码 |
总结
SynchronousQueue的非公平策略的内部实现就是这样,要注意的是对于mode部分状态的处理,通过高位和低位分别区分是否已匹配和是什么类型的操作(生产者还是消费者)。其实需要记住的是其操作必须是成双成对的,在无超时无中断的情况下,一个线程执行入队操作,必然需要另一个线程执行出队操作,此时两操作互相匹配,同时完成操作。
本文转载自: 掘金