等待空 BlockingQueue 的首选方法

Preferable approach for waiting on empty BlockingQueue

我有一个多线程应用程序,其中一个线程将项目放入 BlockingQueue 中,多个线程从中取出项目进行处理。问题是关于从队列中取出物品,目前是这样实现的:

class PriorityQueueWorker<T> implements Runnable {
  private final PriorityBlockingQueue<T> requestQueue;
  private final AtomicBoolean continueExecution;

  @Override
  public void run() {
    do {
      T request = null;
      try {
        request = requestQueue.take();
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        continueExecution.set(false);
      }

      if (request == null) {
        continue;
      }

      //... here we do the work

    } while (continueExecution.get());

}

根据 BlockingQueue.take() 的 JavaDoc,它 检索并删除此队列的头部,必要时等待直到元素可用 PriorityBlockingQueue这意味着线程将在 PriorityBlockingQueue.take() 上被阻塞,直到一个项目出现在队列中:

public E take() throws InterruptedException {
  final ReentrantLock lock = this.lock;
  lock.lockInterruptibly();
  E result;
  try {
    while ((result = dequeue()) == null)
      notEmpty.await();
  } finally {
    lock.unlock();
  }
  return result;
}

另一种实现我们逻辑的方法是使用 PriorityBlockingQueue.poll() which returns null 在空队列的情况下:

public E poll() {
  final ReentrantLock lock = this.lock;
  lock.lock();
  try {
      return dequeue();
  } finally {
      lock.unlock();
  }
}

据我了解,这些方法的区别在于 take() 线程被阻塞在空队列等待中,而 poll() 它们在 poll() 中继续围绕无限循环旋转=21=].

对我来说,队列长时间为空的用例很常见,所以我的问题是从性能的角度来看哪种方法更好:保持线程阻塞(take-approach)或保持它们旋转(轮询方法)?

which approach is better as of performance point of view...?

“性能”对您意味着什么?

轮询空队列的工作人员将尝试使用 100% 的 CPU 核心。这将限制该核心代表任何其他线程工作的可用性。它还消耗功率,并产生热量。但是,如果您的应用程序中的线程数与 CPU 内核的数量仔细匹配,那么它可以使您的工作线程更快地响应新的工作项:当工作项到达时,很有可能一些工作人员已经在核心 运行ning 并且能够立即处理它。

在许多(大多数?)应用程序中,OTOH,应用程序中的其他线程和其他进程 运行ning 都希望 share CPU 个核心。在这种情况下,使用 take() 可以释放 CPU 核心,以便在您的员工无事可做时做其他事情。那也是一种“表演”。另外,使用 take() 意味着您的应用程序将消耗更少的电量(如果您的应用程序可以在电池供电的设备上 运行 就很重要),并且它会产生更少的热量(如果您的应用程序可能是个问题运行 正在数据中心或高温环境中。)take() 的缺点是,当新工作项到达时,OS 需要一些时间才能完成“唤醒”等待的工作线程,以便它可以开始处理它。

IMO:您应该从 take() 开始,并且只有在 确定 时才尝试轮询(即,如果您 已测量 性能)您有问题,并且您确定(再次测量)轮询修复了它。另外,除非您可以保证执行轮询的线程数少于可用内核数,否则不要尝试轮询。