SynchronousQueue:同步Queue,属于线程安全的BlockingQueue的一种,此队列设计的理念类似于"单工模式",对于每个put/offer操作,必须等待一个take/poll操作,类似于我们的现实生活中的"火把传递":一个火把传递地他人,需要2个人"触手可及"才行. 因为这种策略,最终导致队列中并没有一个真正的元素;这是一种pipleline思路的基于queue的"操作传递".
- void put(E o):向队列提交一个元素,阻塞直到其他线程take或者poll此元素.
- boolean offer(E o):向队列中提交一个元素,如果此时有其他线程正在被take阻塞(即其他线程已准备接收)或者"碰巧"有poll操作,那么将返回true,否则返回false.
- E take():获取并删除一个元素,阻塞直到有其他线程offer/put.
- boolean poll():获取并删除一个元素,如果此时有其他线程正在被put阻塞(即其他线程提交元素正等待被接收)或者"碰巧"有offer操作,那么将返回true,否则返回false.
- E peek():总会返回null,硬编码.
这个队列中,对我们有意义的操作时put/take,以及put/offer + take或者put/take + poll,对于无法进入队列的元素,需要有额外的"拒绝"策略支持.
SynchronousQueue经常用来,一端或者双端严格遵守"单工"(单工作者)模式的场景,队列的两个操作端分别是productor和consumer.常用于一个productor多个consumer的场景。
在ThreadPoolExecutor中,通过Executors创建的cachedThreadPool就是使用此类型队列.已确保,如果现有线程无法接收任务(offer失败),将会创建新的线程来执行.
下面为简单的例子:
public class SynchronousQueueTest {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
SynchronousQueue
事实上多次执行此程序,会发现不同的结果,同是注释部分Thread.sleep的时间不同,也会对结果不同.所以在线程池中,我们需要"拒绝策略"的支持.
SynchronousQueue支持2种策略:公平和非公平,默认为非公平.
1) 公平策略:内部实现为TransferQueue,即一个队列.(consumer和productor将会被队列化)
2) 非公平策略:内部实现为TransferStack,即一个stack(内部模拟了一个单向链表,允许闯入行为)
内部实现比较复杂,尽管支持线程安全,但是其内部并没有使用lock(事实上无法使用lock),使用了LockSupport来控制线程,使用CAS来控制栈的head游标(非公平模式下)。
如下为非公平策略下实现
对于consumer而言,其为data消费者,每次take将会检测是否已经有productor的线程在等待,如果有,此时head应该不为null并且head的数据类型为“DATA”(两种类型:DATA 和REQUEST)
此时consumer将消费数据。取消productor的阻塞效果(使用LockSupport.unpark).
如果此时没有productor提供数据,即head为null,此时consumer也将创建一个mode为REQUEST的元素,并阻塞线程。当有productor提供数据时,有productor来取消其阻塞效果。
对于productor而言,情况类似。。当put数据时,如果队列中无元素,和take一样,创建一个node为DATA的元素,并阻塞进程。。
如果队列中有元素,则CAS更新head,使head指向新元素,并不断去tryMatch是否有正在阻塞的尚未take到数据的consumer,如果有,就unpark它,并返回数据,并变更列表的next指针。(有点像俄罗斯方块)
如果队列中没有元素(即没有消费者),则自己阻塞。
每个NODE,都有一个waiter属性,标示此在此元素上操作时阻塞的线程(put操作产生的元素未被消费,那么waiter就是put线程;take产生的元素单无元素消费成功时,那么waiter就是take线程)。