Java CyclicBarrier vs CountDownLatch
1. Introdução
Neste tutorial, compararemosCyclicBarrier eCountDownLatch e tentaremos entender as semelhanças e diferenças entre os dois.
2. Quem são esses?
Quando se trata de concorrência, pode ser desafiador conceituar o que cada um deles pretende realizar.
Em primeiro lugar, ambosCountDownLatch and CyclicBarrier are used for managing multi-threaded applications.
E,they are both intended to express how a given thread or group of threads should wait.
2.1. CountDownLatch
UmCountDownLatch é uma construção em que um encadeamentowaité ativado enquanto outros encadeamentoscount down são travados até atingir zero.
Podemos pensar nisso como um prato em um restaurante que está sendo preparado. Não importa qual cozinheiro prepare quantosn items, o garçom devewait até que todos os itens estejam no prato. Se um prato tivern items, qualquer cozinheiracount down onhará a trava de cada item que colocar no prato.
2.2. CyclicBarrier
UmCyclicBarrier é uma construção reutilizável onde um grupo de threadswaits juntos até todos os threadsarrive. Nesse ponto, a barreira é quebrada e umaction opcionalmente pode ser tomado.
Podemos pensar nisso como um grupo de amigos. Toda vez que planejam comer em um restaurante, decidem um ponto comum onde podem se encontrar. Eleswait para o outro lá, e somente quando todosarrives podem ir ao restaurante para comer juntos.
2.3. Leitura adicional
E para obter mais detalhes sobre cada um deles individualmente, consulte nossos tutoriais anteriores emCountDownLatcheCyclicBarrier respectivamente.
3. Tarefas vs. Tópicos
Vamos mergulhar mais fundo em algumas das diferenças semânticas entre essas duas classes.
Conforme declarado nas definições,CyclicBarrier permite que vários encadeamentos aguardem um pelo outro, enquantoCountDownLatch permite que um ou mais encadeamentos aguardem a conclusão de uma série de tarefas.
Resumindo,CyclicBarrier maintains a count of threads enquantoCountDownLatch maintains a count of tasks.
No código a seguir, definimos umCountDownLatch com uma contagem de dois. Em seguida, chamamoscountDown() duas vezes a partir de um único thread:
CountDownLatch countDownLatch = new CountDownLatch(2);
Thread t = new Thread(() -> {
countDownLatch.countDown();
countDownLatch.countDown();
});
t.start();
countDownLatch.await();
assertEquals(0, countDownLatch.getCount());
Quando a trava chega a zero, a chamada paraawait retorna.
Observe que, neste caso,we were able to have the same thread decrease the count twice.
CyclicBarrier,, porém, é diferente neste ponto.
Semelhante ao exemplo acima, criamos umCyclicBarrier, novamente com uma contagem de dois e chamamosawait() nele, desta vez a partir do mesmo thread:
CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
Thread t = new Thread(() -> {
try {
cyclicBarrier.await();
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
// error handling
}
});
t.start();
assertEquals(1, cyclicBarrier.getNumberWaiting());
assertFalse(cyclicBarrier.isBroken());
A primeira diferença aqui é que os threads que estão esperando são eles mesmos a barreira.
Em segundo lugar, e mais importante,the second await() is useless. A single thread can’t count down a barrier twice.
De fato, comot devewait para outro encadeamento chamarawait() - para trazer a contagem para dois - a segunda chamada det paraawait() não ser invocado até que a barreira já seja quebrada!
Em nosso teste,the barrier hasn’t been crossed because we only have one thread waiting and not the two threads that would be required for the barrier to be tripped. Isso também é evidente no métodocyclicBarrier.isBroken(), que retornafalse.
4. Reutilização
A segunda diferença mais evidente entre essas duas classes é a reutilização. Para elaborar,when the barrier trips in CyclicBarrier, the count resets to its original value.CountDownLatch is different because the count never resets.
No código fornecido, definimos umCountDownLatch com contagem 7 e contamos através de 20 chamadas diferentes:
CountDownLatch countDownLatch = new CountDownLatch(7);
ExecutorService es = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
es.execute(() -> {
long prevValue = countDownLatch.getCount();
countDownLatch.countDown();
if (countDownLatch.getCount() != prevValue) {
outputScraper.add("Count Updated");
}
});
}
es.shutdown();
assertTrue(outputScraper.size() <= 7);
Observamos que, embora 20 threads diferentes chamemcountDown(), a contagem não zera quando chega a zero.
Semelhante ao exemplo acima, definimos umCyclicBarrier com a contagem 7 e esperamos em 20 threads diferentes:
CyclicBarrier cyclicBarrier = new CyclicBarrier(7);
ExecutorService es = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
es.execute(() -> {
try {
if (cyclicBarrier.getNumberWaiting() <= 0) {
outputScraper.add("Count Updated");
}
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
// error handling
}
});
}
es.shutdown();
assertTrue(outputScraper.size() > 7);
Nesse caso, observamos que o valor diminui toda vez que um novo encadeamento é executado, redefinindo o valor original, quando chega a zero.
5. Conclusão
Ao todo,CyclicBarrier eCountDownLatch são ferramentas úteis para sincronização entre vários threads. No entanto, eles são fundamentalmente diferentes em termos da funcionalidade que fornecem. Considere cada um com cuidado ao determinar qual é o certo para o trabalho.
Como de costume, todos os exemplos discutidos podem ser acessadosover on Github.