学习使用BlockingQueue.drainTo()方法

4,032 阅读2分钟

学习使用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()方法是解决生产者消费者问题的一个方便的方法,在这种情况下,生产者线程的速率明显低于消费者线程。在这种情况下,消费者可以从队列中抽取全部或一定数量的项目,并一次性处理它们。