Пример реализации BoundedSemaphore, найденный на сайте Дженкова, использует notify
вместо notifyAll
в методах take и Release. В сценарии с несколькими потребителями и производителями не приведет ли это к тому, что один производитель разбудит другого производителя (аналогично поток-потребитель может разбудить другой поток-потребитель), что приведет к пропущенным сигналам? Имеет ли notifyAll
больше смысла или я что-то упускаю?
public class BoundedSemaphore {
private int signals = 0;
private int bound = 0;
public BoundedSemaphore(int upperBound){
this.bound = upperBound;
}
public synchronized void take() throws InterruptedException{
while(this.signals == bound) wait();
this.signals++;
this.notify();
}
public synchronized void release() throws InterruptedException{
while(this.signals == 0) wait();
this.signals--;
this.notify();
}
}
В шаблоне Производитель/Потребитель используется notifyAll
, чтобы избежать проблемы, связанной с тем, что один потребитель будит другого потребителя. Я ожидал, что в примере кода будет использоваться notifyAll
вместо notify
.
Я согласен. Похоже это плохой пример.
Ваш BoundedSemaphore
эквивалентен ограниченной блокирующей очереди пустых, неразличимых токенов. ИМХО, поэтому уместно говорить о «производителях», которые release()
помещают токены в очередь, и «потребителях», которые take()
помещают токены из очереди.
Этот пример будет работать без проблем в приложении с одним производителем и одним потребителем. Хотя notify()
пробуждает только один ожидающий поток, это нормально, поскольку в программе есть только один поток, который может ожидать.
Это не будет работать в приложении с несколькими производителями или несколькими потребителями именно из-за того, что вы сказали: производитель может разбудить другого производителя, или потребитель может разбудить другого потребителя. Использование notifyAll
исправило бы это, но есть способ получше....
...Вместо использования synchronized
и встроенного монитора вашего BoundedSemaphore
класса вы можете использовать объект ReentrantLock для синхронизации потоков.
Одним из преимуществ ReentrantLock является то, что вы можете получить два отдельных объекта Condition для блокировки. Один объект Condition будет ожидаться только производителями и уведомляться только потребителями, а другой будет ожидаться только потребителями и уведомляться только производителями. Затем вы вызываете сигнальный метод условия, который ведет себя аналогично Object.notify()
, и он пробуждает не более одного ожидающего потока, но гарантированно всегда пробуждает поток нужного типа.
public class BoundedSemaphore {
private int signals = 0;
private int bound = 0;
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition_p = lock.newCondition();
private final Condition condition_c = lock.newCondition();
public BoundedSemaphore(int upperBound){
this.bound = upperBound;
}
public void take() throws InterruptedException{
lock.lock();
try {
while(this.signals == bound) condition_c.await();
this.signals++;
condition_p.signal();
}
finally {
lock.unlock();
}
}
public void release() throws InterruptedException{
lock.lock();
try {
while(this.signals == 0) condition_p.await();
this.signals--;
condition_c.signal();
}
finally {
lock.unlock();
}
}
}
Примечание. Вы могли бы сделать этот код чище, если бы у вас была какая-то оболочка AutoCloseable для блокировки lock
и вы использовали оператор try-with-resources, чтобы разблокировать ее. Я оставлю это в качестве упражнения для читателя.
Спасибо. Это проясняет ситуацию. Я также видел, как проблема с несколькими производителями/потребителями решается с использованием двух семафоров и мьютекса (для синхронизированного доступа к буферу), что, я думаю, аналогично тому, как вы предложили. stackoverflow.com/questions/74681788/…
@xyzcoder, ОК, но вашей заявленной целью было создание собственного класса, подобного семафору. Используется ли семафоры для реализации подсчета семафоров?
Ты прав. Вы ответили на мой первоначальный вопрос. В качестве дополнения к этому, как только у меня будет готовая собственная реализация семафора (ваш код), и мне будет интересно, буду ли я использовать ее в сценарии с несколькими производителями и потребителями, мне понадобятся 2 семафора или достаточно 1 семафора?
@xyzcoder, Re: "Мне нужно 2 семафора или 1...?" Не знаю, как я мог бы на это ответить — семафоры нужны не мне, — но, возможно, это могло бы стать темой нового вопроса.
Вы вообще читали документацию по этим методам? Она вполне понятна.