学习使用BlockingQueue.drainTo()方法,将一个队列(从队列中轮询所有或特定数量的元素)排入一个集合**。当多个生产者线程向BlockingQueue中添加元素,而一个消费者线程定期从队列中轮询多个项目并一起处理它们时,就需要排水。
一个这样的例子是一个excel报告生成器。可能有一个ExecutorService,有多个线程在处理记录,并把它们放到阻塞队列中。还有一个报告编写者线程定期轮询队列并在excel中写入记录。
1.阻塞队列drainTo() 方法
- drainTo()从指定的队列中移除所有可用的元素,并将它们添加到给定的集合中。
- 它提供了比逐一轮询所有元素更好的性能。
- 如果在排水过程中,所提供的集合被修改,那么这个方法的行为将无法定义。
- 如果集合是不可变的,该方法将抛出UnsupportedOperationException。
- 对于通用集合,不兼容的类类型将导致ClassCastException。
这个方法有两个版本。第二个方法最多耗尽maxElements的可用元素数。
int drainTo(Collection c)
int drainTo(Collection c, int maxElements)
2.演示
我们正在创建两个任务来演示drainTo()方法的用法。为了保持简单,让我们调用生产者和消费者。生产者将在一个阻塞队列中不断添加项目,而消费者将耗尽项目,重复地,经过一些延迟。
public class Producer implements Runnable {
BlockingQueue queue;
public Producer(BlockingQueue queue){
this.queue = queue;
}
@Override
@SneakyThrows
public void run() {
while (true){
Thread.sleep(2000);
System.out.println("Produced new message at : " + LocalDateTime.now());
queue.offer("Test Message");
}
}
}
public class Consumer implements Runnable {
BlockingQueue queue;
public Consumer(BlockingQueue queue){
this.queue = queue;
}
@Override
@SneakyThrows
public void run() {
while (true) {
Thread.sleep(10000);
List<String> messages = new ArrayList<>();
System.out.println("=========================================");
System.out.println("Queue size before draining : " + queue.size());
int messagesCount = queue.drainTo(messages, 20);
System.out.println("Collection size : " + messagesCount);
//messages.stream().forEach(System.out::println);
System.out.println("Queue size after draining : " + queue.size());
System.out.println("=========================================");
}
}
}
下面的代码创建了一个ExecutorService并启动了两个生产者线程和一个消费者线程。消费者线程每10秒执行一次,并从队列中耗尽所有的消息。
public class QueueDrain {
public static void main(String[] args) {
BlockingQueue<String> queue = new ArrayBlockingQueue(20);
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.submit(new Producer(queue));
executorService.submit(new Producer(queue));
executorService.submit(new Consumer(queue));
}
}
该程序的输出如下。两个生产者线程以2秒的间隔各产生一条消息,因此共有10条消息。消费者线程每隔10秒就会轮询所有的消息。
Produced new message at : 2022-08-10T15:45:58.627532600
Produced new message at : 2022-08-10T15:45:58.627532600
Produced new message at : 2022-08-10T15:46:00.631044400
Produced new message at : 2022-08-10T15:46:00.631044400
Produced new message at : 2022-08-10T15:46:02.646342
Produced new message at : 2022-08-10T15:46:02.646342
Produced new message at : 2022-08-10T15:46:04.647652800
Produced new message at : 2022-08-10T15:46:04.647790800
=========================================
Queue size before draining : 8
Collection size : 8
Queue size after draining : 0
=========================================
3.总结
BlockingQueue的drainTo()方法是解决生产者消费者问题的一个方便的方法,在这种情况下,生产者线程的速率明显低于消费者线程。在这种情况下,消费者可以从队列中抽取全部或一定数量的项目,并一次性处理它们。