天天看点

Java并发包中的同步队列SynchronousQueue实现原理

不像arrayblockingqueue或linkedlistblockingqueue,synchronousqueue内部并没有数据缓

存空间,你不能调用peek()方法来看队列中是否有数据元素,因为数据元素只有当你试着取走的时候才可能存在,不取走而只想偷窥一下是不行的,当然遍历

这个队列的操作也是不允许的。队列头元素是第一个排队要插入数据的线程,而不是要交换的数据。数据是在配对的生产者和消费者线程之间直接传递的,并不会将数据缓冲数据到队列中。可以这样来理解:生产者和消费者互相等待对方,握手,然后一起离开。

synchronousqueue的一个使用场景是在线程池里。executors.newcachedthreadpool()就使用了

synchronousqueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。

阻塞队列的实现方法有许多:

阻塞算法实现通常在内部采用一个锁来保证多个线程中的put()和take()方法是串行执行的。采用锁的开销是比较大的,还会存在一种情况是线程

a持有线程b需要的锁,b必须一直等待a释放锁,即使a可能一段时间内因为b的优先级比较高而得不到时间片运行。所以在高性能的应用中我们常常希望规避锁

的使用。

经典同步队列实现采用了三个信号量,代码很简单,比较容易理解:

在多核机器上,上面方法的同步代价仍然较高,操作系统调度器需要上千个时间片来阻塞或唤醒线程,而上面的实现即使在生产者put()时已经有一个消费者在等待的情况下,阻塞和唤醒的调用仍然需要。

java 5的实现相对来说做了一些优化,只使用了一个锁,使用队列代替信号量也可以允许发布者直接发布数据,而不是要首先从阻塞在信号量处被唤醒。

算法。性能比java5的实现有较大提升。竞争机制支持公平和非公平两种:非公平竞争模式使用的数据结构是后进先出栈(lifo

stack);公平竞争模式则使用先进先出队列(fifo

queue),性能上两者是相当的,一般情况下,fifo通常可以支持更大的吞吐量,但lifo可以更大程度的保持线程的本地化。

代码实现里的dual queue或stack内部是用链表(linkedlist)来实现的,其节点状态为以下三种情况:

持有数据 – put()方法的元素

持有请求 – take()方法

这个算法的特点就是任何操作都可以根据节点的状态判断执行,而不需要用到锁。

其核心接口是transfer,生产者的put或消费者的take都使用这个接口,根据第一个参数来区别是入列(栈)还是出列(栈)。

transferqueue实现如下(摘自java 6源代码),入列和出列都基于spin和cas方法: