创建一个模拟信号量的类,但允许的数量不得超过0

问题描述

我遇到了一个使用信号量设计队列的问题,以便所有获取该信号的线程都必须等待,直到某个线程释放它们为止。但是这里要注意的是,如果在没有线程等待的情况下调用release,那么它不像实际的信号量那样会产生任何效果,在实际的信号量中将添加额外的许可。我开始尝试这样的事情:

public class QueueOfThreads {
    
    private Semaphore valve = new Semaphore(0);
    volatile int count = 0;
    
    
    public void acquire() throws InterruptedException {
        synchronized(this) {
            count++;
        }
        valve.acquire();
    }
    
    public void release() {
        synchronized(this) {
            if(count > 0) {
                valve.release();
                count--;
            }
            else {
                System.out.println("will not release since no thread is waiting");
            }
        }
    }

}

但是我可以看到这是错误的,因为如果在count++之后抢占了一个线程,那么可以在获取之前调用该释放。

我花了很多时间试图找到一种方法,以确保在任何发行版之前至少调用了一次获取。但是我总是会遇到同样的问题,在获取信号量之后,由于当前线程将处于等待状态,因此我无法向其他线程发出有关获取信号量的信号。但是,如果我在获取信号量之前发出信号,则可以在实际获取信号量之前抢占线程。

请让我知道是否可以编写这样的类以及如何做?

这个问题来自艾伦·唐尼(Allen B. Downey)的一本名为《信号量小书》的书中的评论,其中提到:

“信号量也可用于表示队列。在这种情况下,初始值为0,通常编写代码,以便除非有线程等待,否则不可能发信号,因此信号量永远不会是积极的。”

解决方法

您可以利用Object.notify()来释放一个等待线程(如果有):

public class QueueOfThreads {

  public synchronized void acquire() throws InterruptedException {
    wait();
  }

  public synchronized void release() {
    notify();
  }
}

但是,这仅适用于没有虚假唤醒的JVM。 如果可能发生虚假唤醒,则实现会更加复杂:

public class QueueOfThreads {
  int threadCount = 0;
  boolean notified = false;

  public synchronized void acquire() throws InterruptedException {
    threadCount++;
    do {
      wait();
    } while (!notified);
    threadCount--;
    notified = false;
  }

  public synchronized void release() {
    if (threadCount==0) {
      return;
    }
    notified = true;
    notify();
  }
}