CyclicBarrier u Javi

1. Uvod

Ciklične zapreke su sinhronizacijski konstrukti koji su uvedeni s Javom 5 kao dio java.util.concurrent paket.

U ovom ćemo članku istražiti ovu implementaciju u istodobnom scenariju.

2. Java istodobnost - sinkronizatori

The java.util.concurrent paket sadrži nekoliko klasa koje pomažu u upravljanju skupom niti koje međusobno surađuju. Neki od njih uključuju:

  • CyclicBarrier
  • Phaser
  • CountDownLatch
  • Izmjenjivač
  • Semafor
  • SynchronousQueue

Ove klase nude izravno funkcionalnost za uobičajene obrasce interakcije između niti.

Ako imamo skup niti koje međusobno komuniciraju i sliče jednom od uobičajenih uzoraka, možemo jednostavno ponovno upotrijebiti odgovarajuće knjižnične klase (također nazvane Sinkronizatori) umjesto da pokušate smisliti prilagođenu shemu pomoću skupa brava i objekata stanja i sinkronizirano ključna riječ.

Usredotočimo se na CyclicBarrier ide naprijed.

3. CyclicBarrier

A CyclicBarrier je sinkronizator koji omogućuje nizu niti da čekaju jedni druge da dosegnu zajedničku točku izvršenja, koja se također naziva a prepreka.

Ciklične zapreke koriste se u programima u kojima imamo fiksni broj niti koje moraju pričekati da jedna drugoj dođu do zajedničke točke prije nastavka izvršavanja.

Pregrada se zove ciklički jer se može ponovno upotrijebiti nakon otpuštanja niti na čekanju.

4. Upotreba

Konstruktor za a CyclicBarrier je jednostavno. Potreban je jedan cijeli broj koji označava broj niti koje trebaju nazvati čekati() metoda na instanci barijere koja označava dostizanje zajedničke točke izvršenja:

javna CyclicBarrier (int party)

Pozvane su i niti koje trebaju sinkronizirati njihovo izvršavanje stranke i pozivajući čekati() metoda je kako možemo registrirati da je određena nit dosegla točku barijere.

Ovaj poziv je sinkron i nit koja poziva ovu metodu obustavlja izvršavanje sve dok određeni broj niti ne pozove istu metodu na barijeri. Ova situacija u kojoj je pozvan potreban broj niti čekati(), Zove se spotaknuvši se o barijeru.

Po želji drugi argument možemo proslijediti konstruktoru, a Izvodljivo primjer. Ovo ima logiku koju bi pokrenula zadnja nit koja prelazi prepreku:

javna CyclicBarrier (int party, Runnable barrierAction)

5. Provedba

Vidjeti CyclicBarrier na djelu, razmotrimo sljedeći scenarij:

Postoji operacija koju izvodi fiksni broj niti i sprema odgovarajuće rezultate na popis. Kada sve niti završe s izvršavanjem svoje radnje, jedna od njih (obično zadnja koja pređe prepreku) započinje s obradom podataka koje je dohvatio svaki od njih.

Primijenimo glavnu klasu u kojoj se događa sva radnja:

javna klasa CyclicBarrierDemo {private CyclicBarrier cyclicBarrier; privatni Popis djelomični rezultati = Collections.synchronizedList (novi ArrayList ()); private Random random = novi Random (); privatni int NUM_PARTIAL_RESULTS; privatni int NUM_WORKERS; // ...}

Ovaj je razred prilično naprijed - NUM_WORKERS je broj niti koje će se izvršiti i NUM_PARTIAL_RESULTS je broj rezultata koje će proizvesti svaka radnička nit.

Napokon, jesmo djelomičniRezultati to su popis koji će pohraniti rezultate svake od ovih radničkih niti. Imajte na umu da je ovaj popis a SynchronizedList jer će na nju istovremeno pisati više niti, a dodati() metoda nije sigurna za nit na ravnici ArrayList.

Sada ćemo implementirati logiku svake radničke niti:

javna klasa CyclicBarrierDemo {// ... klasa NumberCruncherThread implementira Runnable {@Override public void run () {String thisThreadName = Thread.currentThread (). getName (); Popis djelomičnog rezultata = novi ArrayList (); // Izmrvite neke brojeve i pohranite djelomični rezultat za (int i = 0; i <NUM_PARTIAL_RESULTS; i ++) {Integer num = random.nextInt (10); System.out.println (thisThreadName + ": drobljenje nekih brojeva! Konačni rezultat -" + num); djelomični rezultat.add (broj); } djelomični rezultati.add (djelomični rezultat); isprobajte {System.out.println (thisThreadName + "čeka da drugi dođu do barijere."); cyclicBarrier.await (); } catch (InterruptedException e) {// ...} catch (BrokenBarrierException e) {// ...}}}}

Sada ćemo implementirati logiku koja se pokreće kad se prepreka aktivira.

Da stvari budu jednostavne, samo dodajte sve brojeve s popisa djelomičnih rezultata:

javna klasa CyclicBarrierDemo {// ... klasa AggregatorThread implementira Runnable {@Override public void run () {String thisThreadName = Thread.currentThread (). getName (); System.out.println (thisThreadName + ": Računski zbroj radnika" + NUM_WORKERS + ", koji imaju" + NUM_PARTIAL_RESULTS + "rezultata."); int zbroj = 0; za (Popis niti rezultata: djelomični rezultati) {System.out.print ("Dodavanje"); za (Integer djelomični rezultat: threadResult) {System.out.print (djelomični rezultat + ""); zbroj + = djelomični rezultat; } System.out.println (); } System.out.println (thisThreadName + ": Konačni rezultat =" + zbroj); }}}

Posljednji korak bio bi izgradnja CyclicBarrier i pokreni stvari s a glavni() metoda:

javna klasa CyclicBarrierDemo {// Prethodni kôd public void runSimulation (int numWorkers, int numberOfPartialResults) {NUM_PARTIAL_RESULTS = numberOfPartialResults; NUM_WORKERS = numWorkers; cyclicBarrier = novi CyclicBarrier (NUM_WORKERS, novi AggregatorThread ()); System.out.println ("Mriještenje" + NUM_WORKERS + "radničke niti za izračunavanje" + NUM_PARTIAL_RESULTS + "po parcijalni rezultati"); for (int i = 0; i <NUM_WORKERS; i ++) {Radnik niti = nova nit (novi NumberCruncherThread ()); worker.setName ("Nit" + i); worker.start (); }} javna statička void glavna (String [] args) {CyclicBarrierDemo demo = new CyclicBarrierDemo (); demo.runSimulation (5, 3); }} 

U gornjem kodu inicijalizirali smo cikličku barijeru s 5 niti koje svaka proizvode po 3 cijele brojeve kao dio svog izračunavanja i pohranjuju iste na rezultirajući popis.

Jednom kada se prepreka aktivira, zadnja nit koja je aktivirala barijeru izvršava logiku navedenu u AggregatorThread, naime - zbraja sve brojeve proizvedene u nitima.

6. Rezultati

Evo izlaza iz jednog izvršavanja gore navedenog programa - svako izvršavanje može stvoriti različite rezultate jer se niti mogu stvoriti u različitom redoslijedu:

Mrijest 5 radničkih niti za izračunavanje po 3 djelomična rezultata. Nit 0: Krckanje nekih brojeva! Konačni rezultat - 6 Tema 0: drobljenje nekih brojeva! Konačni rezultat - 2 Tema 0: drobljenje nekih brojeva! Konačni rezultat - 2 nit 0 koja čeka da drugi dođu do barijere. Tema 1: drobljenje nekih brojeva! Konačni rezultat - 2 Tema 1: drobljenje nekih brojeva! Konačni rezultat - 0 Tema 1: drobljenje nekih brojeva! Konačni rezultat - 5 nit 1 koja čeka da drugi dođu do barijere. Tema 3: drobljenje nekih brojeva! Konačni rezultat - 6 Tema 3: drobljenje nekih brojeva! Konačni rezultat - 4 Tema 3: drobljenje nekih brojeva! Konačni rezultat - 0 Tema 3 koja čeka da drugi dođu do barijere. Tema 2: drobljenje nekih brojeva! Konačni rezultat - 1 Tema 2: drobljenje nekih brojeva! Konačni rezultat - 1 Tema 2: drobljenje nekih brojeva! Konačni rezultat - 0 nit 2 koja čeka da drugi dođu do barijere. Tema 4: drobljenje nekih brojeva! Konačni rezultat - 9 Tema 4: drobljenje nekih brojeva! Konačni rezultat - 3 Tema 4: drobljenje nekih brojeva! Konačni rezultat - 5 nit 4 koja čeka da drugi dođu do barijere. Tema 4: Izračunavanje konačnog zbroja od 5 radnika, sa po 3 rezultata. Dodavanje 6 2 2 Dodavanje 2 0 5 Dodavanje 6 4 0 Dodavanje 1 1 0 Dodavanje 9 3 5 Tema 4: Konačni rezultat = 46 

Kao što gornji izlaz pokazuje, Tema 4 je onaj koji prelazi prepreku i također izvršava konačnu logiku agregacije. Također nije potrebno da se niti zapravo izvode redom kojim su pokrenute, kao što pokazuje gornji primjer.

7. Zaključak

U ovom smo članku vidjeli što CyclicBarrier je, i u kakvim je situacijama korisno.

Također smo implementirali scenarij u kojem nam je trebao fiksni broj niti da bismo stigli do fiksne točke izvršenja, prije nego što smo nastavili s drugom programskom logikom.

Kao i uvijek, kôd vodiča možete pronaći na GitHubu.