不像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方法: