我可以在 Java 中使用信号量实现阻塞队列吗?

Can I implement blocking queue using Semaphore in Java?

请问是否可以使用Semaphore来实现阻塞队列?

在下面的代码中,我使用一个信号量来保护临界区,另外两个信号量对象来跟踪空槽和填充对象的数量。

public class BlockingQueue {
  private List<Object> queue = new LinkedList<Object>();
  private int limit;
  private Semaphore slots; // semaphore for empty slots
  private Semaphore objs;  // semaphore for filled slots
  private Semaphore mutex; // for the critical section
  public BlockingQueue(int limit) {
    this.limit = limit;
    this.slots = new Semaphore(limit); // initial empty slot = capacity
    this.objs = new Semaphore(0);
    this.mutex = new Semaphore(1);
  }
  private void enqueue(Object o) throws InterruptedException {
    slots.acquire();
    mutex.acquire(); // critical section starts
    queue.add(o);
    mutex.release(); // critical section ends
    objs.release();
  }
  private Object dequeue() throws InterruptedException {
    objs.acquire();
    mutex.acquire(); // critical section starts
    Object o = queue.remove(0);
    mutex.release(); // critical section ends
    slots.release();
    return o;
  }
}

添加到之前的评论 - 我们可以同意您的代码有效(这是一个众所周知的算法),特别是您在保护 LinkedList 方面是正确的,因为它不是内部线程安全的。

但是,如果您将代码与 java util 实现进行比较 http://grepcode.com/file_/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/concurrent/ArrayBlockingQueue.java/?v=source 它可能会提出一些要考虑的问题:

  1. 请 Google 讨论 "ReentrantLock versus Binary Semaphore":它们都创建互斥锁并保护 临界区,但前者更好描述你的意图,加上它可能更容易为将来的维护。例如,一个程序员同事不会意外地释放一个没有获得 ReentrantLock 的线程

  2. Google 关于 "semaphore versus condition variable" 的讨论:两者都允许您 "wait for something to become available",但条件变量可能更通用,而且您可以将所有条件绑定到单个锁(如 java util 代码所做的那样)。我假设这对性能有一些轻微影响,加上您需要处理未来需求的方式,例如 中断 、超时、崩溃。这不会使你的代码"wrong",它只是供你考虑的东西。

未经测试,我会说这是可行的。 但是,每次release()都会通知阻塞在acquire()中的线程。因此,您实际上至少具有与重入锁+条件相同的成本,可能更糟,因为有 2 次获取和 2 次释放()调用。

信号量是实现互斥&同步的另一种方式。 二进制信号量或互斥量可用于锁定和解锁临界区。 通用信号量或计数信号量可用于跟踪空闲槽和占用槽。