线程安全的不存储元素的阻塞队列SynchronousQueue
SynchronousQueue是一个不存储元素的阻塞队列,每一个 put 操作必须等待 take 操作,否则不能继续添加元素。支持公平锁和非公平锁2种策略来访问队列。默认是采用非公平性策略访问队列。公平性策略底层使用了类似队列的数据结构,而非公平策略底层使用了类似栈的数据结构。SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue。
队列创建
SynchronousQueue<Integer> queue = new SynchronousQueue<Integer>();
应用场景
SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合传递性场景。
SynchronousQueue 的一个使用场景是在线程池里。Executors.newCachedThreadPool() 就使用了 SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。
我们来看一个具体的例子:
public class TestSynchronousQueue {
public static void main(String[] args) {
SynchronousQueue<Integer> queue = new SynchronousQueue<>();
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
Thread t1 = new Thread(consumer);
Thread t2 = new Thread(producer);
t1.start();
t2.start();
}
}
/**
* 模拟生产者
*/
class Producer implements Runnable {
SynchronousQueue<Integer> queue = null;
public Producer(SynchronousQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
int rand = new Random().nextInt(1000);
System.out.println(String.format("模拟生产者:%d", rand));
try {
TimeUnit.SECONDS.sleep(3);
queue.put(rand);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(queue.isEmpty());
}
}
/**
* 模拟消费者
*/
class Consumer implements Runnable {
SynchronousQueue<Integer> queue = null;
public Consumer(SynchronousQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
System.out.println("消费者已经准备好接收元素了...");
try {
System.out.println(String.format("消费一个元素:%d", queue.take()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("================================================");
}
}
工作原理
由于SynchronousQueue的支持公平策略和非公平策略,所以底层有两种数据结构
- 队列(实现公平策略),有一个头结点和尾结点,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;
- 栈(实现非公平策略),有一个头结点(为默认策略),同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。
队列与栈都是通过链表来实现的。具体的数据结构如下:
参考文章: |