CountDownLatch – blokujące odliczanie

Pakiet java.util.concurrent wprowadził wiele ciekawych i ułatwiających codzienną pracę rozwiązań. Jednym z nich jest klasa CountDownLatch, która jest tematem tego wpisu. Jest to implementacja, która może być wykorzystana do bezpiecznego wątkowo odliczania. Możecie się zastanawiać do czego może przydać się tak prosty mechanizm jak odliczanie, zapraszam więc do wpisu po wyjaśnienia.

Problem

Wyobraźmy sobie sytuację, w której mamy trzy serwisy. Każdy z nich wykonuje asynchroniczną pracę polegającą na obliczaniu sumy użytkowników każdego z nich. Na koniec obliczeń każdego z tych serwisów chcielibyśmy zsumować ich wyniki i podać średnią liczbę użytkowników:

Problemem w tej sytuacji jest to, iż w jakiś sposób chcielibyśmy uruchomić obliczanie średniej dopiero wtedy, gdy trzy asynchroniczne obliczenia się zakończą.

CountDownLatch

Jednym z rozwiązań tego problemu jest zastosowanie klasy CountDownLatch. Jest to tak zwany “zatrzask”, który otwiera się, gdy licznik osiągnie zero. Na początku poprzez konstruktor ustalamy do jakiej liczby ma odbywać się odliczanie (czyli w naszym przypadku ile będzie asynchronicznych zadań):

CountDownLatch cdl = new CountDownLatch(3);

Teraz możemy “zablokować” nasz serwis zliczający średnią. Blokowanie (metoda await()) będzie polegało na tym, iż praca nie zostanie odpalona, dopóki wartość licznika CountDownLatch nie będzie równa zero.

cdl.await();

Natomiast każdy serwis po wykonaniu swojej pracy ma za zadanie zmniejszyć ten licznik (metoda countDown()):

cdl.countDown();

No dobra, to ustawiamy licznik na trzy i uruchamiamy zadania:

Pierwsze zadanie się skończyło, wywoływana jest metoda countDown(), która zmniejsza licznik:

Drugie i trzecie zadanie również się zakończyły (po zakończeniu zostały wywołane metody countDown()). Blokada na metodzie await() została zwolniona i uruchomiła się logika odpowiedzialna za zliczanie średniej:

Bezpieczeństwo

Należy pamiętać, aby umieszczać metodę countDown() w bloku finally. Ponadto dla bezpieczeństwa powinniśmy korzystać z metody await(), która przyjmuje maksymalny czas oczekiwania boolean await(long timeout, TimeUnit unit). Dzięki temu unikniemy dead lock’a w przypadku, gdy w jednym z zadań coś pójdzie nie tak i wartość licznika nie zostanie zmniejszona.

Github

Całość jak zawsze na GitHubie.