CyclicBarrier i Java

1. Introduktion

Cykliske barrierer er synkroniseringskonstruktioner, der blev introduceret med Java 5 som en del af java.util.concurrent pakke.

I denne artikel undersøger vi denne implementering i et samtidighedsscenarie.

2. Java-samtidighed - Synkronisatorer

Det java.util.concurrent pakken indeholder flere klasser, der hjælper med at administrere et sæt tråde, der samarbejder med hinanden. Nogle af disse inkluderer:

  • Cyklisk barriere
  • Phaser
  • CountDownLatch
  • Veksler
  • Semafor
  • SynchronousQueue

Disse klasser tilbyder ud af boksen funktionalitet til almindelige interaktionsmønstre mellem tråde.

Hvis vi har et sæt tråde, der kommunikerer med hinanden og ligner et af de almindelige mønstre, vi kan simpelthen genbruge de relevante biblioteksklasser (også kaldet Synkroniseringer) i stedet for at prøve at komme med et brugerdefineret skema ved hjælp af et sæt låse og tilstandsobjekter og synkroniseret nøgleord.

Lad os fokusere på Cyklisk barriere fremadrettet.

3. Cyklisk barriere

EN Cyklisk barriere er en synkroniser, der giver et sæt tråde mulighed for at vente på hinanden for at nå et fælles udførelsespunkt, også kaldet a barriere.

Cykliske barrierer bruges i programmer, hvor vi har et fast antal tråde, der skal vente på, at hinanden når et fælles punkt, før de fortsætter med at udføre.

Barrieren kaldes cyklisk fordi det kan genbruges, når de ventende tråde er frigivet.

4. Anvendelse

Konstruktøren til en Cyklisk barriere er simpelt. Det tager et enkelt heltal, der angiver antallet af tråde, der skal ringe til vente() metode på barriereinstansen for at betegne at nå det fælles udførelsespunkt:

offentlig CyclicBarrier (int parter)

De tråde, der skal synkronisere deres udførelse, kaldes også fester og kalder vente() metode er, hvordan vi kan registrere, at en bestemt tråd har nået barrierepunktet.

Dette opkald er synkron, og den tråd, der kalder denne metode, suspenderer udførelsen, indtil et bestemt antal tråde har kaldt den samme metode på barrieren. Denne situation, hvor det krævede antal tråde har kaldt vente(), Hedder tripping barrieren.

Eventuelt kan vi videregive det andet argument til konstruktøren, som er a Kan køres eksempel. Dette har logik, der vil blive kørt af den sidste tråd, der udløser barrieren:

public CyclicBarrier (int parties, Runnable barrierAction)

5. Implementering

At se Cyklisk barriere Lad os overveje følgende scenarie i aktion:

Der er en handling, hvor et fast antal tråde udfører og gemmer de tilsvarende resultater i en liste. Når alle tråde er færdige med at udføre deres handling, begynder en af ​​dem (typisk den sidste, der udløser barrieren), at behandle de data, der blev hentet af hver af disse.

Lad os implementere hovedklassen, hvor al handling sker:

offentlig klasse CyclicBarrierDemo {private CyclicBarrier cyclicBarrier; privat liste partialResults = Collections.synchronizedList (ny ArrayList ()); privat tilfældig tilfældig = ny tilfældig (); privat int NUM_PARTIAL_RESULTS; privat int NUM_WORKERS; // ...}

Denne klasse er ret ligetil - NUM_WORKERS er antallet af tråde, der skal udføres, og NUM_PARTIAL_RESULTS er antallet af resultater, som hver af arbejdertrådene vil producere.

Endelig har vi det partialResults der er en liste, der vil gemme resultaterne af hver af disse arbejdstråde. Bemærk, at denne liste er en Synkroniseret liste fordi flere tråde vil skrive til det på samme tid, og tilføje() metoden er ikke trådsikker på en almindelig ArrayList.

Lad os nu implementere logikken for hver af arbejdstrådene:

public class CyclicBarrierDemo {// ... class NumberCruncherThread implementerer Runnable {@Override public void run () {String thisThreadName = Thread.currentThread (). getName (); Liste partialResult = ny ArrayList (); // Knus nogle tal, og gem delresultatet for (int i = 0; i <NUM_PARTIAL_RESULTS; i ++) {Integer num = random.nextInt (10); System.out.println (thisThreadName + ": Knusende nogle tal! Endeligt resultat -" + num); partialResult.add (num); } partialResults.add (partialResult); prøv {System.out.println (thisThreadName + "venter på, at andre når barrieren."); cyclicBarrier.await (); } fangst (InterruptedException e) {// ...} fangst (BrokenBarrierException e) {// ...}}}}

Vi implementerer nu den logik, der kører, når barrieren er udløst.

For at gøre tingene enkle, lad os bare tilføje alle numrene i listen over delresultater:

public class CyclicBarrierDemo {// ... klasse AggregatorThread implementerer Runnable {@Override public void run () {String thisThreadName = Thread.currentThread (). getName (); System.out.println (thisThreadName + ": Computing sum af" + NUM_WORKERS + "arbejdstagere med" + NUM_PARTIAL_RESULTS + "resultater hver."); int sum = 0; for (List threadResult: partialResults) {System.out.print ("Tilføjer"); for (Integer partialResult: threadResult) {System.out.print (partialResult + ""); sum + = partialResult; } System.out.println (); } System.out.println (thisThreadName + ": Endeligt resultat =" + sum); }}}

Det sidste trin ville være at konstruere Cyklisk barriere og spar ting med en hoved () metode:

offentlig klasse CyclicBarrierDemo {// Tidligere kode public void runSimulation (int numWorkers, int numberOfPartialResults) {NUM_PARTIAL_RESULTS = numberOfPartialResults; NUM_WORKERS = antal arbejdere; cyclicBarrier = ny CyclicBarrier (NUM_WORKERS, ny AggregatorThread ()); System.out.println ("Spawning" + NUM_WORKERS + "arbejdstråde til beregning af" + NUM_PARTIAL_RESULTS + "delresultater hver"); for (int i = 0; i <NUM_WORKERS; i ++) {Thread worker = new Thread (new NumberCruncherThread ()); worker.setName ("tråd" + i); worker.start (); }} public static void main (String [] args) {CyclicBarrierDemo demo = new CyclicBarrierDemo (); demo.runSimulation (5, 3); }} 

I ovenstående kode initialiserede vi den cykliske barriere med 5 tråde, der hver producerer 3 heltal som en del af deres beregning og gemmer det samme på den resulterende liste.

Når barrieren er udløst, udfører den sidste tråd, der udløb barrieren, den logik, der er angivet i AggregatorThread, nemlig - tilføj alle de numre, der produceres af trådene.

6. Resultater

Her er output fra en udførelse af ovenstående program - hver udførelse kan skabe forskellige resultater, da trådene kan skabes i en anden rækkefølge:

Gyder 5 arbejdertråde til at beregne 3 delresultater hver Tråd 0: Knuser nogle tal! Slutresultat - 6 Tråd 0: Knusende nogle tal! Slutresultat - 2 Tråd 0: Knusende nogle tal! Slutresultat - 2 tråd 0 venter på, at andre når barrieren. Tråd 1: Knus nogle numre! Slutresultat - 2 Tråd 1: Knusende nogle tal! Slutresultat - 0 Tråd 1: Knusende nogle tal! Slutresultat - 5 tråd 1 venter på, at andre når barrieren. Tråd 3: Knus nogle numre! Slutresultat - 6 Tråd 3: Knusende nogle tal! Slutresultat - 4 Tråd 3: Knusende nogle tal! Slutresultat - 0 Tråd 3 venter på, at andre når barrieren. Tråd 2: Knusende nogle tal! Slutresultat - 1 Tråd 2: Knusende nogle tal! Slutresultat - 1 Tråd 2: Knusende nogle tal! Slutresultat - 0 Tråd 2 venter på, at andre når barrieren. Tråd 4: Knusende nogle tal! Slutresultat - 9 Tråd 4: Knusende nogle tal! Slutresultat - 3 Tråd 4: Knusende nogle tal! Slutresultat - 5 Tråd 4 venter på, at andre når barrieren. Tråd 4: Beregner den endelige sum af 5 arbejdere, der har 3 resultater hver. Tilføjelse 6 2 2 Tilføjelse 2 0 5 Tilføjelse 6 4 0 Tilføjelse 1 1 0 Tilføjelse 9 3 5 Tråd 4: Slutresultat = 46 

Som ovenstående output viser, Tråd 4 er den, der løsner barrieren og også udfører den endelige aggregeringslogik. Det er heller ikke nødvendigt, at tråde faktisk køres i den rækkefølge, de startes, som ovenstående eksempel viser.

7. Konklusion

I denne artikel så vi, hvad en Cyklisk barriere er, og hvilken slags situationer det er nyttigt i.

Vi implementerede også et scenario, hvor vi havde brug for et fast antal tråde for at nå et fast eksekveringspunkt, inden vi fortsatte med anden programlogik.

Som altid kan koden til vejledningen findes på GitHub.