CountDownLatch против семафора

95

Есть ли преимущество в использовании

java.util.concurrent.CountdownLatch

вместо того

java.util.concurrent.Semaphore ?

Насколько я могу судить, следующие фрагменты практически эквивалентны:

1. Семафор

final Semaphore sem = new Semaphore(0);
for (int i = 0; i < num_threads; ++ i)
{
  Thread t = new Thread() {
    public void run()
    {
      try
      {
        doStuff();
      }
      finally
      {
        sem.release();
      }
    }
  };
  t.start();
}

sem.acquire(num_threads);

2: CountDownLatch

final CountDownLatch latch = new CountDownLatch(num_threads);
for (int i = 0; i < num_threads; ++ i)
{
  Thread t = new Thread() {
    public void run()
    {
      try
      {
        doStuff();
      }
      finally
      {
        latch.countDown();
      }
    }
  };
  t.start();
}

latch.await();

За исключением того, что в случае № 2 защелка не может быть использована повторно и, что более важно, вам нужно заранее знать, сколько потоков будет создано (или дождитесь, пока все они будут запущены, прежде чем создавать защелку).

Так в какой ситуации защелка может быть предпочтительнее?

Finnw
источник

Ответы:

112

CountDownLatchчасто используется как полная противоположность вашему примеру. Как правило, у вас будет много блокируемых потоков, await()которые будут запускаться одновременно, когда обратный отсчет достигнет нуля.

final CountDownLatch countdown = new CountDownLatch(1);

for (int i = 0; i < 10; ++ i) {
   Thread racecar = new Thread() {    
      public void run() {
         countdown.await(); //all threads waiting
         System.out.println("Vroom!");
      }
   };
   racecar.start();
}
System.out.println("Go");
countdown.countDown();   //all threads start now!

Вы также можете использовать это как «барьер» в стиле MPI, который заставляет все потоки ждать, пока другие потоки догонят определенную точку, прежде чем продолжить.

final CountDownLatch countdown = new CountDownLatch(num_thread);

for (int i = 0; i < num_thread; ++ i) {
   Thread t= new Thread() {    
      public void run() {
         doSomething();
         countdown.countDown();
         System.out.printf("Waiting on %d other threads.",countdown.getCount());
         countdown.await();     //waits until everyone reaches this point
         finish();
      }
   };
   t.start();
}

Тем не менее, CountDownLatchможно безопасно использовать способ, который вы показали в своем примере.

Джеймс Шек
источник
1
Спасибо. Итак, два моих примера не были бы эквивалентными, если бы несколько потоков могли ждать защелки ... если только sem.acquire (num_threads); за ним следует sem.release (num_threads) ;? Я думаю, это снова сделало бы их эквивалентными.
finnw
В некотором смысле да, если каждый поток вызывает получение, за которым следует освобождение. Собственно говоря, нет. С защелкой все потоки могут запускаться одновременно. С семафором они становятся доступными один за другим (что может привести к другому планированию потоков).
Джеймс Шек
Документация Java, похоже, подразумевает, что CountdownLatch хорошо сочетается с его примером: docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/… . В частности, «CountDownLatch, инициализированный значением N, может использоваться, чтобы заставить один поток ждать, пока N потоков не завершат какое-либо действие или какое-то действие не будет выполнено N раз».
Крис Моррис
Ты прав. Я немного обновлю свой ответ, чтобы отразить, что это наиболее распространенные варианты использования CountDownLatch, которые я видел, по сравнению с предполагаемым использованием.
Джеймс Шек
11
Это отвечает на вопрос: « Как чаще всего используется CountDownLatch?» Он не отвечает на исходный вопрос о преимуществах / различиях использования CountDownLatch по сравнению с семафором.
Марко Лацкович
67

CountDownLatch используется для запуска серии потоков, а затем ожидания, пока все они не будут завершены (или пока они не вызовут countDown()заданное количество раз.

Семафор используется для управления количеством параллельных потоков, использующих ресурс. Этот ресурс может быть чем-то вроде файла или может быть ЦП, ограничивая количество выполняемых потоков. Счетчик семафора может увеличиваться и уменьшаться по мере того, как разные потоки вызывают acquire()и release().

В вашем примере вы по существу используете семафор как своего рода защелку Count UP . Учитывая, что ваше намерение состоит в том, чтобы дождаться завершения всех потоков, использование CountdownLatchделает ваше намерение более ясным.

Mtruesdell
источник
23

Краткое содержание:

  1. Semaphoreи CountDownLatchслужит другой цели.

  2. Используйте Semaphoreдля управления доступом потока к ресурсу.

  3. Используйте CountDownLatchдля ожидания завершения всех потоков

Semaphore определение из Javadocs:

A Semaphoreподдерживает набор разрешений. Каждый acquire()блокирует при необходимости до получения разрешения , а затем принимает его. Каждый release()добавляет разрешение, потенциально освобождая блокирующего покупателя.

Однако фактические объекты разрешений не используются; Semaphoreпросто продолжает подсчет числа имеющихся и действует соответствующим образом .

Как это работает?

Семафоры используются для управления количеством параллельных потоков, использующих ресурс. Этот ресурс может быть чем-то вроде общих данных, блоком кода ( критический раздел ) или любым файлом.

Счетчик a Semaphoreможет увеличиваться и уменьшаться по мере того, как разные потоки вызывают acquire()и release(). Но в любой момент у вас не может быть большего количества потоков, чем количество семафоров.

Semaphore Сценарии использования:

  1. Ограничение одновременного доступа к диску (это может снизить производительность из-за конкурирующих запросов к диску)
  2. Ограничение создания потока
  3. Пул / ограничение соединений JDBC
  4. Регулирование сетевого подключения
  5. Регулирование задач, интенсивно использующих ЦП или память

Взгляните на эту статью для использования семафоров.

CountDownLatch определение из Javadocs:

Средство синхронизации, которое позволяет одному или нескольким потокам ждать, пока не завершится набор операций, выполняемых в других потоках.

Как это работает?

CountDownLatchработает за счет инициализации счетчика числом потоков, которое уменьшается каждый раз, когда поток завершает свое выполнение. Когда счетчик достигает нуля, это означает, что все потоки завершили свое выполнение, и поток, ожидающий защелки, возобновляет выполнение.

CountDownLatch Сценарии использования:

  1. Достижение максимального параллелизма: иногда нам нужно запустить несколько потоков одновременно, чтобы достичь максимального параллелизма.
  2. Дождитесь завершения N потоков перед началом выполнения
  3. Обнаружение тупиковых ситуаций.

Прочтите эту статью, чтобы CountDownLatchчетко понять концепции.

Взгляните также на Fork Join Pool в этой статье . Он имеет некоторое сходство с CountDownLatch.

Равиндра бабу
источник
7

Допустим, вы зашли в магазин профессиональных гольфистов в надежде найти четверку,

Когда вы стоите в очереди, чтобы получить время у одного из профессиональных продавцов магазина, по сути, вы звонили proshopVendorSemaphore.acquire(), как только у вас появляется время proshopVendorSemaphore.release()первого , вы звоните. Примечание: любой из бесплатных помощников может обслужить вас, то есть общий ресурс.

Теперь вы подходите к стартеру, он начинается CountDownLatch(4)и звонки await()ждать других, со своей стороны вы называетесь зарегистрированным т.е. CountDownLatch. countDown()и то же самое делает остальная четверка. Когда все приедут, стартер дает ( await()звонок возвращается)

Теперь, после девяти лунок, когда каждый из вас делает перерыв, гипотетически позволяет снова задействовать стартера, он использует «новичок» CountDownLatch(4)для выхода из лунки 10, такое же ожидание / синхронизация, как и в лунке 1.

Однако, если бы стартер использовал a CyclicBarrierдля начала, он мог бы сбросить тот же экземпляр в лунке 10 вместо второй защелки, которая использует & throw.

Радж Шринивас
источник
1
Я не уверен, что понимаю ваш ответ, но если вы пытаетесь описать, как работают CountdownLatch и Semaphore, это не является предметом вопроса.
finnw 01
10
К сожалению, я ничего не знаю о гольфе.
кольцо предъявитель
но стартовый материал можно также сделать с помощью .acquire (Players) и увеличения количества релизов с релизом. обратный отсчет, кажется, просто имеет меньше функций и не имеет возможности повторного использования.
Ласси Киннунен
1

Если посмотреть на свободно доступный источник, в реализации этих двух классов нет ничего волшебного, поэтому их производительность должна быть примерно одинаковой. Выберите тот, который делает ваши намерения более очевидными.

Том Хотин - tackline
источник
0

CountdownLatchзаставляет потоки ждать await()метода, пока счетчик не достигнет нуля. Так что, возможно, вы хотите, чтобы все ваши потоки ждали до трех вызовов чего-либо, тогда все потоки могут уйти. А Latchвообще сбросить нельзя.

A Semaphoreпозволяет потокам получать разрешения, что предотвращает одновременное выполнение слишком большого количества потоков, блокируя, если он не может получить разрешения, необходимые для продолжения. Разрешения можно вернуть, Semaphoreразрешив другим ожидающим потокам продолжить работу.

Спенсер Кормос
источник