问题描述
我遇到了一个使用信号量设计队列的问题,以便所有获取该信号的线程都必须等待,直到某个线程释放它们为止。但是这里要注意的是,如果在没有线程等待的情况下调用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();
}
}