Использование notifyAll вместо notify в реализации BoundedSemaphore

Пример реализации 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.

Вы вообще читали документацию по этим методам? Она вполне понятна.

Just another Java programmer 31.08.2024 10:53
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
1
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я согласен. Похоже это плохой пример.

Ваш 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 01.09.2024 03:10

@xyzcoder, ОК, но вашей заявленной целью было создание собственного класса, подобного семафору. Используется ли семафоры для реализации подсчета семафоров?

Ohm's Lawman 01.09.2024 14:39

Ты прав. Вы ответили на мой первоначальный вопрос. В качестве дополнения к этому, как только у меня будет готовая собственная реализация семафора (ваш код), и мне будет интересно, буду ли я использовать ее в сценарии с несколькими производителями и потребителями, мне понадобятся 2 семафора или достаточно 1 семафора?

xyzcoder 01.09.2024 20:24

@xyzcoder, Re: "Мне нужно 2 семафора или 1...?" Не знаю, как я мог бы на это ответить — семафоры нужны не мне, — но, возможно, это могло бы стать темой нового вопроса.

Ohm's Lawman 01.09.2024 21:41

Другие вопросы по теме

Похожие вопросы

Как использовать файл JAR для отображения заставки?
Функции PHP и Java возвращают разные даты при расчете на 6 месяцев вперед
Файл журнала Log4J остается пустым при программной настройке
Почему JPA все еще работает с классом записи как с сущностью?
Приложение Spring Framework возвращает только ошибки 404
Spring: получение ошибки грамматики неправильного sql для именованного параметра – удаление параметра устраняет ошибку
Есть ли в Visual Studio Code сочетания клавиш, которые переключают детали предложения и всплывающие окна режима объяснения в предложениях?
Вывод консоли IntelliJ IDEA неправильно отображает символы вертикальной табуляции (U + 000B)
Ошибка создания bean-компонента с именем «entityManagerFactory», определенным в ресурсе пути к классу: невозможно построить Hibernate SessionFactory;
Как использовать внешние файлы конфигурации для обеспечения специфичных для среды конфигураций в приложении Spring Boot?