SynchronousQueue源码解析
- 前言
这是一个阻塞队列,每一个线程的插入操作都要等待另一个对应的删除操作。这个同步队列没有任何的容量,甚至没有一个元素的容量。你不能查看同步队列中的元素,因为想要查看一个元素必须是在正在尝试删除一个元素的时候。你不能向队列中插入元素,除非有另外一个线程正在删除队列中的元素。这个队列不能使用迭代器迭代遍历元素,因为这个同步队列中没有元素可以遍历。它们非常适合于切换设计,在这种设计中,在一个线程中运行的对象必须与在另一个线程中运行的对象同步,以便传递一些信息、事件或任务。这个队列支持可选的公平策略对生产者线程池和消费者线程排序,默认情况下是非公平策略,设置了公平策略可以保证先进先出的顺序。
非公平策略和公平策略的执行形式基本上是一样的,非公平策略队列用的是stack(Lifo后进先出),公平策略队列用的是queue(Fifo先进先出),先进先出队列在竞争下通常支持更高的吞吐量,但是Lifo在公共应用程序中维护更高的线程局部性。
公平策略和非公平策略的队列都继承了一个静态抽象内部类Transferer,两种策略都要实现transfer方法。
先来看看非公平策略模式
非公平策略的stack中的节点使用是SNode,来看一下这个静态内部类?
SNode的变量
volatile SNode next; // next node in stack
volatile SNode match; // the node matched to this
volatile Thread waiter; // to control park/unpark
Object item; // data; or null for REQUESTs
int mode;
FIFO类型的SynchronousQueue的执行流程
FIFO源码比较简单,这里就不在介绍,LIFO类型的就是比较饶了,我是琢磨了一段时间才弄明白,不同的任务之间是如何进行匹配的,并且在匹配过程中是如何完成数据传递的。
这里先直接上代码,讲解完代码之后,在总结一个流程图,加油,相信自己只有熬过最难熬的过程,说明你才有机会进步。
LIFO的意思使用的是栈的数据结构,所有具有后进先出的效果。
LIFO的主类是TransferStack,
有4个重要的变量:
这三个变量用来表示栈中节点SNODE的模式,REQUEST 表示请求获取数据的操作,DATA 表示想队列中添加数据的操作,FULFILLING 表示当前SNODE正在匹配的过程中,希望其他线程不要干扰。
这个变量是栈的头结点,这里使用的是volatile,在多线程并发的环境下实现同步,对多线程保持可见性。
在来看看栈中节点的数据结构是啥样的?
在来看看核心方法transfer(),由于代码比较长,所以这里分三种情况进行解析。
- 当前栈为空或者当前的节点的模式和head节点的模式相同,那么当前节点入栈等待匹配。
- 当前节点和head节点的模式是互补的,也就是说一个节点是到栈取元素的,一个节点是到栈添加元素的节点,判断这两个节点互补的方法是isFulfilling(int),先来看看这个是如何实现的?
m是NODE的模式,只有三种0 、1、2
看一下计算结果:
0 & 2 即 0000 & 0010 = 0000 = 0
1 & 2 即 0001 & 0010 = 0000 = 0
2 & 2 即 0010 & 0010 = 0010 = 2
所以 isFulfilling 只有m=2时,才会返回true,这个标志位的作用是显示的表明当前节点正在完成匹配,其他线程请勿干扰。
需要注意的地方就是,当当前节点正处于匹配的时候,这个时候当前节点的模式的计算公式如下:
计算结果如下:
0 | 2 即 0000 | 0010 = 0010 = 2
1 | 2 即 0001 | 0010 = 0011 = 3
2 | 2 即 0010 | 0010 = 0010 = 2
接下来就可以解析源码啦?有点困了,额,在加把劲!!!
最后看一看,node进入阻塞是怎么处理的?
整个LIFO的transfer至此已经完成,JDK1.8源码解析又完成了一个,开始进入下一个吧!!!