Vodič za CountDownLatch na Javi

1. Uvod

U ovom ćemo članku dati vodič za CountDownLatch predavanja i pokazati kako se to može koristiti u nekoliko praktičnih primjera.

U osnovi, pomoću a CountDownLatch možemo uzrokovati blokadu niti sve dok druge niti ne izvrše zadani zadatak.

2. Korištenje u istodobnom programiranju

Jednostavno rečeno, a CountDownLatch ima brojač polje koje možete umanjiti prema potrebi. Tada ga možemo koristiti za blokiranje pozivajuće niti dok se ne odbroji na nulu.

Da radimo paralelnu obradu, mogli bismo instancirati CountDownLatch s istom vrijednošću za brojač kao i niz niti preko kojih želimo raditi. Tada bismo mogli samo nazvati odbrojavanje () nakon što se završi svaka nit, jamčeći da se poziva ovisna nit čekati() će blokirati dok se ne završe radničke niti.

3. Čekajući da se niz niti dovrši

Isprobajmo ovaj obrazac stvaranjem a Radnik i pomoću a CountDownLatch polje za signalizaciju kad je dovršeno:

public class Worker implementira Runnable {private List outputScraper; privatni CountDownLatch countDownLatch; javni radnik (Popis outputScraper, CountDownLatch countDownLatch) {this.outputScraper = outputScraper; this.countDownLatch = countDownLatch; } @Override public void run () {doSomeWork (); outputScraper.add ("Odbrojano"); countDownLatch.countDown (); }}

Zatim, napravimo test kako bismo dokazali da možemo dobiti CountDownLatch čekati Radnik instance za dovršetak:

@Test public void whenParallelProcessing_thenMainThreadWillBlockUntilCompletion () baca InterruptedException {List outputScraper = Collections.synchronizedList (new ArrayList ()); CountDownLatch countDownLatch = novi CountDownLatch (5); Popis radnika = Stream .generate (() -> nova nit (novi Worker (outputScraper, countDownLatch))) .limit (5) .collect (toList ()); worker.forEach (Tema :: start); countDownLatch.await (); outputScraper.add ("Otpušten zasun"); assertThat (outputScraper) .containsEhactly ("Odbrojano", "Odbrojano", "Odbrojano", "Odbrojano", "Odbrojano", "Otpušteno zasun"); }

Prirodno, "Otpušten zasun" uvijek će biti zadnji izlaz - jer ovisi o CountDownLatch puštajući.

Imajte na umu da ako nismo nazvali čekati(), ne bismo mogli garantirati redoslijed izvođenja niti, pa bi test slučajno propao.

4. Skup nit koji čeka na početak

Ako smo uzeli prethodni primjer, ali ovaj put započeli smo tisuće niti umjesto pet, vjerojatno će mnogi od ranijih završiti obradu prije nego što smo uopće nazvali početak() na onim kasnijim. To bi moglo otežati pokušaj reprodukcije problema s istodobnošću, jer ne bismo mogli pokrenuti sve naše niti paralelno.

Da bismo to zaobišli, uzmimo CountdownLatch raditi drugačije nego u prethodnom primjeru. Umjesto da blokiramo nadređenu nit dok neke podređene niti ne završe, možemo blokirati svaku podređenu nit dok sve ostale ne počnu.

Izmijenimo svoj trčanje() metoda pa blokira prije obrade:

javna klasa WaitingWorker implementira Runnable {private List outputScraper; privatni CountDownLatch readyThreadCounter; privatni CountDownLatch pozivanjeThreadBlocker; privatni CountDownLatchpleteThreadCounter; javni WaitingWorker (navesti outputScraper, CountDownLatch readyThreadCounter, CountDownLatch pozivanjeThreadBlocker, CountDownLatchpleteThreadCounter) {this.outputScraper = outputScraper; this.readyThreadCounter = readyThreadCounter; this.callingThreadBlocker = callThreadBlocker; this.completedThreadCounter = completeThreadCounter; } @Override public void run () {readyThreadCounter.countDown (); pokušajte {callThreadBlocker.await (); doSomeWork (); outputScraper.add ("Odbrojano"); } catch (InterruptedException e) {e.printStackTrace (); } napokon {completedThreadCounter.countDown (); }}}

Sada, izmijenimo naš test tako da blokira sve Radnici su započeli, deblokira Radnici, a zatim blokira do Radnici su završili:

@Test public void whenDoingLotsOfThreadsInParallel_thenStartThemAtTheSameTime () baca InterruptedException {List outputScraper = Collections.synchronizedList (new ArrayList ()); CountDownLatch readyThreadCounter = novi CountDownLatch (5); CountDownLatch pozivanjeThreadBlocker = novo CountDownLatch (1); CountDownLatch completeThreadCounter = novi CountDownLatch (5); Popis radnika = Stream .generate (() -> nova nit (novi WaitingWorker (outputScraper, readyThreadCounter, pozivanjeThreadBlocker, completeThreadCounter))) .limit (5) .collect (toList ()); worker.forEach (Tema :: start); readyThreadCounter.await (); outputScraper.add ("Radnici spremni"); pozivanjeThreadBlocker.countDown (); pleteThreadCounter.await (); outputScraper.add ("Radnici dovršeni"); assertThat (outputScraper) .containsEhactly ("Radnici spremni", "Odbrojani", "Odbrojani", "Odbrojani", "Odbrojani", "Odbrojani", "Radnici završeni"); }

Ovaj je obrazac stvarno koristan za pokušaj reprodukcije istodobnih pogrešaka, jer se može koristiti za prisiljavanje tisuća niti da pokušaju paralelno pokušati izvesti neku logiku.

5. Završetak a CountdownLatch Rano

Ponekad možemo doći u situaciju da Radnici završi greškom prije odbrojavanja CountDownLatch. To bi moglo rezultirati time da nikad ne dosegne nulu i čekati() nikad ne završava:

@Override public void run () {if (true) {throw new RuntimeException ("O dragi, ja sam slomljeni radnik"); } countDownLatch.countDown (); outputScraper.add ("Odbrojano"); }

Izmijenimo naš raniji test da bismo koristili a BrokenWorker, kako bi se pokazalo kako čekati() blokirat će zauvijek:

@Test public void whenFailingToParallelProcess_thenMainThreadShouldGetNotGetStuck () baca InterruptedException {List outputScraper = Collections.synchronizedList (new ArrayList ()); CountDownLatch countDownLatch = novo CountDownLatch (5); Popis radnika = Stream .generate (() -> nova nit (novi BrokenWorker (outputScraper, countDownLatch))) .limit (5) .collect (toList ()); worker.forEach (Tema :: start); countDownLatch.await (); }

Jasno je da to nije ponašanje koje želimo - bilo bi puno bolje da se aplikacija nastavi nego beskonačno blokirati.

Da bismo to zaobišli, dodajmo argument za vremensko ograničenje našem pozivu na čekati().

logička vrijednost dovršena = countDownLatch.await (3L, TimeUnit.SECONDS); assertThat (završeno) .isFalse ();

Kao što vidimo, test će s vremenom isteći i čekati() će se vratiti lažno.

6. Zaključak

U ovom smo kratkom vodiču pokazali kako možemo koristiti a CountDownLatch kako bi se nit blokirao dok druge niti ne završe neku obradu.

Pokazali smo i kako se može koristiti za pomoć u otklanjanju pogrešaka u istodobnosti osiguravajući paralelno izvođenje niti.

Implementacija ovih primjera može se naći na GitHub-u; ovo je projekt zasnovan na Mavenu, pa bi ga trebalo biti lako pokrenuti kakav jest.