vent og underret () Metoder i Java

1. Introduktion

I denne artikel ser vi på en af ​​de mest grundlæggende mekanismer i Java - trådsynkronisering.

Vi diskuterer først nogle vigtige samtidige termer og metoder.

Og vi udvikler en simpel applikation - hvor vi skal håndtere problemer med samtidighed med det formål at få en bedre forståelse vente() og underrette().

2. Trådsynkronisering i Java

I et multitrådet miljø kan flere tråde forsøge at ændre den samme ressource. Hvis tråde ikke styres ordentligt, vil dette naturligvis føre til problemer med konsistens.

2.1. Beskyttede blokke i Java

Et værktøj, vi kan bruge til at koordinere handlinger af flere tråde i Java - er beskyttede blokke. Sådanne blokke kontrollerer en bestemt tilstand, før de genoptager udførelsen.

Med det i tankerne vil vi gøre brug af:

  • Object.wait () - at suspendere en tråd
  • Objekt. Underret () - at vække en tråd op

Dette kan bedre forstås ud fra det følgende diagram, der viser livscyklussen for en Tråd:

Bemærk, at der er mange måder at kontrollere denne livscyklus på; i denne artikel vil vi dog kun fokusere på vente() og underrette().

3. Den vente() Metode

Kort sagt, når vi ringer vent () - dette tvinger den aktuelle tråd til at vente, indtil en anden tråd påberåber sig underrette() eller notifyAll () på det samme objekt.

Til dette skal den aktuelle tråd eje objektets skærm. Ifølge Javadocs kan dette ske, når:

  • vi har henrettet synkroniseret eksempel metode til det givne objekt
  • vi har henrettet kroppen af ​​en synkroniseret blokere for det givne objekt
  • ved at udføre synkroniseret statisk metoder til objekter af typen Klasse

Bemærk, at kun en aktiv tråd kan eje et objekts skærm ad gangen.

Det her vente() metoden leveres med tre overbelastede underskrifter. Lad os se på disse.

3.1. vente()

Det vente() metode får den aktuelle tråd til at vente på ubestemt tid, indtil en anden tråd enten påberåber sig underrette() til dette objekt eller notifyAll ().

3.2. vent (lang timeout)

Ved hjælp af denne metode kan vi specificere et timeout, hvorefter tråden automatisk vækkes. En tråd kan vækkes, inden den når timeout ved hjælp af underrette() eller notifyAll ().

Bemærk, at du ringer vent (0) er det samme som at ringe vente().

3.3. Vent (lang timeout, int nanoer)

Dette er endnu en signatur, der giver den samme funktionalitet, hvor den eneste forskel er, at vi kan give højere præcision.

Den samlede timeout-periode (i nanosekunder) beregnes som 1_000_000 * timeout + nano.

4. underret () og notifyAll ()

Det underrette() metoden bruges til at vække tråde, der venter på adgang til dette objekts skærm.

Der er to måder at underrette ventetråde på.

4.1. underrette()

For alle tråde, der venter på dette objekts skærm (ved hjælp af en af vente() metode), metoden underrette() underretter nogen af ​​dem om at vågne vilkårligt. Valget af nøjagtigt hvilken tråd, der skal vækkes, er ikke-deterministisk og afhænger af implementeringen.

Siden underrette() vækker en enkelt tilfældig tråd, den kan bruges til at implementere gensidig eksklusiv låsning, hvor tråde udfører lignende opgaver, men i de fleste tilfælde ville det være mere levedygtigt at implementere notifyAll ().

4.2. notifyAll ()

Denne metode vækker simpelthen alle tråde, der venter på dette objekts skærm.

De vækkede tråde udfyldes på den sædvanlige måde - som enhver anden tråd.

Men inden vi lader deres henrettelse fortsætte, altid definere en hurtig kontrol for den nødvendige betingelse for at fortsætte med tråden - fordi der kan være nogle situationer, hvor tråden blev vækket uden at modtage en underretning (dette scenarie diskuteres senere i et eksempel).

5. Afsender-modtager-synkroniseringsproblem

Nu hvor vi forstår det grundlæggende, lad os gå igennem et simpelt AfsenderModtager applikation - der vil gøre brug af vente() og underrette() metoder til at indstille synkronisering mellem dem:

  • Det Afsender formodes at sende en datapakke til Modtager
  • Det Modtager kan ikke behandle datapakken, før Afsender er færdig med at sende den
  • Tilsvarende er Afsender må ikke forsøge at sende en anden pakke, medmindre Modtager har allerede behandlet den forrige pakke

Lad os først oprette Data klasse, der består af dataene pakke der sendes fra Afsender til Modtager. Vi bruger vente() og notifyAll () at indstille synkronisering mellem dem:

offentlig klasse Data {privat strengpakke; // Sandt, hvis modtageren skulle vente // Falsk, hvis afsenderen skulle vente privat boolsk overførsel = sand; offentlig synkroniseret ugyldig sending (strengpakke) {mens (! overførsel) {prøv {vent (); } fange (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("Tråden afbrudt", e); }} overførsel = falsk; this.packet = pakke; notifyAll (); } offentlig synkroniseret strengmodtagelse () {mens (overførsel) {prøv {vent (); } fange (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("Tråden afbrudt", e); }} overførsel = sand; notifyAll (); returpakke; }}

Lad os nedbryde, hvad der foregår her:

  • Det pakke variabel angiver de data, der overføres over netværket
  • Vi har en boolsk variabel overførsel - som den Afsender og Modtager vil bruge til synkronisering:
    • Hvis denne variabel er rigtigt, derefter Modtager skulle vente på Afsender for at sende beskeden
    • Hvis det er falsk, derefter Afsender skulle vente på Modtager for at modtage beskeden
  • Det Afsender anvendelser sende() metode til at sende data til Modtager:
    • Hvis overførsel er falsk, vi venter ved at ringe vente() på denne tråd
    • Men når det er rigtigt, skifter vi status, indstiller vores besked og ringer notifyAll () at vække andre tråde for at specificere, at en væsentlig begivenhed er sket, og de kan kontrollere, om de kan fortsætte med at udføre
  • Tilsvarende er Modtager vil bruge modtage() metode:
    • Hvis den overførsel blev sat til falsk ved Afsender, så fortsætter kun det, ellers ringer vi vente() på denne tråd
    • Når betingelsen er opfyldt, skifter vi status, underretter alle ventende tråde om at vågne op og returnere den datapakke, der var Modtager

5.1. Hvorfor vedlægge vente() i en mens Loop?

Siden underrette() og notifyAll () vækker tilfældigt tråde, der venter på dette objekts skærm, det er ikke altid vigtigt, at betingelsen er opfyldt. Nogle gange kan det ske, at tråden er vækket, men betingelsen er faktisk ikke opfyldt endnu.

Vi kan også definere en check for at redde os fra falske wakeups - hvor en tråd kan vågne op af at vente uden nogensinde at have modtaget en underretning.

5.2. Hvorfor skal vi synkronisere sende() og modtage() Metoder?

Vi placerede disse metoder indeni synkroniseret metoder til tilvejebringelse af indre låse. Hvis en tråd ringer vente() metoden ejer ikke den iboende lås, en fejl smides.

Vi opretter nu Afsender og Modtager og implementere Kan køres interface på begge, så deres forekomster kan udføres af en tråd.

Lad os først se hvordan Afsender vil arbejde:

offentlig klasse Afsender implementerer Runnable {private data data; // standard constructors public void run () {String packets [] = {"First packet", "Second packet", "Third packet", "Fourth packet", "End"}; for (String pakke: pakker) {data.send (pakke); // Thread.sleep () for at efterligne tunge processer på serversiden, prøv {Thread.sleep (ThreadLocalRandom.current (). NextInt (1000, 5000)); } fange (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("Tråden afbrudt", e); }}}}

For det Afsender:

  • Vi opretter nogle tilfældige datapakker, der sendes over hele netværket pakker [] array
  • For hver pakke ringer vi blot til sende()
  • Så ringer vi Thread.sleep () med tilfældigt interval for at efterligne tunge processer på serversiden

Lad os endelig implementere vores Modtager:

public class Receiver implementerer Runnable {private Data load; // standard konstruktører offentlig ugyldig kørsel () {for (String receivedMessage = load.receive ();! "End" .equals (receivedMessage); receivedMessage = load.receive ()) {System.out.println (receivedMessage); // ... prøv {Thread.sleep (ThreadLocalRandom.current (). nextInt (1000, 5000)); } fange (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("Tråden afbrudt", e); }}}}

Her ringer vi simpelthen load.receive () i løkken, indtil vi får det sidste "Ende" datapakke.

Lad os nu se denne applikation i aktion:

public static void main (String [] args) {Data data = new Data (); Trådafsender = ny tråd (ny afsender (data)); Trådmodtager = ny tråd (ny modtager (data)); sender.start (); receiver.start (); }

Vi modtager følgende output:

Første pakke Anden pakke Tredje pakke Fjerde pakke 

Og her er vi - vi har modtaget alle datapakker i den rigtige rækkefølge og med succes etableret den korrekte kommunikation mellem vores afsender og modtager.

6. Konklusion

I denne artikel diskuterede vi nogle kernesynkroniseringskoncepter i Java; mere specifikt fokuserede vi på, hvordan vi kan bruge vente() og underrette() til at løse interessante synkroniseringsproblemer. Og til sidst gennemgik vi en kodeeksempel, hvor vi anvendte disse begreber i praksis.

Før vi afslutter her, er det værd at nævne, at alle disse API'er på lavt niveau, f.eks vente(), underrette() og notifyAll () - er traditionelle metoder, der fungerer godt, men mekanismer på højere niveau er ofte enklere og bedre - såsom Java's native Låse og Tilstand grænseflader (tilgængelig i java.util.concurrent.locks pakke).

For mere information om java.util.concurrent pakke, besøg vores oversigt over java.util.concurrent artiklen og Låse og Tilstand er dækket af guiden til java.util.concurrent.Locks, her.

Som altid er de komplette kodestykker, der bruges i denne artikel, tilgængelige på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found