wacht en meld () Methoden in Java

1. Inleiding

In dit artikel zullen we kijken naar een van de meest fundamentele mechanismen in Java: synchronisatie van threads.

We zullen eerst enkele essentiële termen en methodologieën met betrekking tot gelijktijdigheid bespreken.

En we zullen een eenvoudige applicatie ontwikkelen - waarin we problemen met gelijktijdigheid aanpakken, met als doel een beter begrip wacht() en melden ().

2. Threadsynchronisatie in Java

In een omgeving met meerdere threads kunnen meerdere threads proberen dezelfde bron te wijzigen. Als threads niet goed worden beheerd, leidt dit natuurlijk tot consistentieproblemen.

2.1. Bewaakte blokken in Java

Een tool die we kunnen gebruiken om acties van meerdere threads in Java te coördineren - zijn bewaakte blokken. Dergelijke blokken controleren een bepaalde conditie voordat ze de uitvoering hervatten.

Met dat in gedachten maken we gebruik van:

  • Object.wait () - om een ​​draad op te schorten
  • Object.notify () - om een ​​draad wakker te maken

Dit kan beter worden begrepen uit het volgende diagram, dat de levenscyclus van een Draad:

Houd er rekening mee dat er veel manieren zijn om deze levenscyclus te beheersen; in dit artikel gaan we ons echter alleen richten op wacht() en melden ().

3. Het wacht() Methode

Simpel gezegd, als we bellen wacht() - dit dwingt de huidige thread om te wachten tot een andere thread wordt aangeroepen melden () of informerenAll () op hetzelfde object.

Hiervoor moet de huidige thread de monitor van het object bezitten. Volgens Javadocs kan dit gebeuren wanneer:

  • we hebben geëxecuteerd gesynchroniseerd instantiemethode voor het opgegeven object
  • we hebben het lichaam van een gesynchroniseerd blok op het gegeven object
  • door uit te voeren gesynchroniseerde statische methoden voor objecten van het type Klasse

Merk op dat slechts één actieve thread tegelijk de eigenaar van een objectmonitor kan zijn.

Dit wacht() methode wordt geleverd met drie overbelaste handtekeningen. Laten we deze eens bekijken.

3.1. wacht()

De wacht() methode zorgt ervoor dat de huidige thread voor onbepaalde tijd wacht totdat een andere thread wordt aangeroepen melden () voor dit object of informerenAll ().

3.2. wacht (lange time-out)

Met behulp van deze methode kunnen we een time-out specificeren waarna de thread automatisch wordt gewekt. Een thread kan worden gewekt voordat de time-out is bereikt met melden () of meldAll ().

Merk op dat bellen wacht (0) is hetzelfde als bellen wacht().

3.3. wacht (lange time-out, int nanos)

Dit is nog een andere handtekening die dezelfde functionaliteit biedt, met het enige verschil dat we een hogere precisie kunnen bieden.

De totale time-outperiode (in nanoseconden) wordt berekend als 1_000_000 * time-out + nanos.

4. melden () en informerenAll ()

De melden () methode wordt gebruikt om threads wakker te maken die wachten op toegang tot de monitor van dit object.

Er zijn twee manieren om wachtende threads te melden.

4.1. melden ()

Voor alle threads die wachten op de monitor van dit object (door een van de wacht() methode), de methode melden () meldt een van hen om willekeurig wakker te worden. De keuze van welke thread precies moet worden gewekt, is niet-deterministisch en hangt af van de implementatie.

Sinds melden () maakt een enkele willekeurige thread wakker het kan worden gebruikt om elkaar uitsluitende vergrendeling te implementeren waar threads vergelijkbare taken uitvoeren, maar in de meeste gevallen zou het meer haalbaar zijn om te implementeren informerenAll ().

4.2. informerenAll ()

Deze methode wekt eenvoudig alle threads die op de monitor van dit object wachten.

De ontwaakte threads worden op de gebruikelijke manier voltooid, net als elke andere thread.

Maar voordat we toestaan ​​dat hun executie doorgaat, altijd definieer een snelle controle voor de voorwaarde die nodig is om verder te gaan met de thread - omdat er situaties kunnen zijn waarin de thread werd gewekt zonder een melding te ontvangen (dit scenario wordt later in een voorbeeld besproken).

5. Probleem met synchronisatie van zender en ontvanger

Nu we de basis begrijpen, laten we een eenvoudig doornemen AfzenderOntvanger applicatie - die gebruik zal maken van de wacht() en melden () methoden om synchronisatie tussen hen in te stellen:

  • De Afzender moet een datapakket naar de Ontvanger
  • De Ontvanger kan het datapakket niet verwerken totdat het Afzender is klaar met verzenden
  • Evenzo is het Afzender mag niet proberen om een ​​ander pakket te verzenden, tenzij het Ontvanger heeft het vorige pakket al verwerkt

Laten we eerst creëren Gegevens klasse die bestaat uit de gegevens pakket dat wordt verzonden vanaf Afzender naar Ontvanger. We zullen gebruiken wacht() en informerenAll () om een ​​synchronisatie tussen hen in te stellen:

openbare klasse Data {privé String-pakket; // Waar als de ontvanger moet wachten // Niet waar als de afzender moet wachten private boolean transfer = true; openbare gesynchroniseerde ongeldige verzending (String-pakket) {while (! transfer) {try {wait (); } catch (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("Thread onderbroken", e); }} transfer = false; this.packet = pakket; meldAll (); } openbare gesynchroniseerde String ontvangst () {while (transfer) {probeer {wait (); } catch (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("Thread onderbroken", e); }} transfer = true; meldAll (); retourpakket; }}

Laten we eens kijken wat er hier aan de hand is:

  • De pakket variabele geeft de gegevens aan die via het netwerk worden overgedragen
  • We hebben een boolean variabele overdracht - welke de Afzender en Ontvanger zal gebruiken voor synchronisatie:
    • Als deze variabele waar, dan de Ontvanger zou moeten wachten Afzender om het bericht te verzenden
    • Als het is false, dan Afzender zou moeten wachten Ontvanger om het bericht te ontvangen
  • De Afzender toepassingen sturen() methode om gegevens naar de Ontvanger:
    • Als overdracht is vals, we wachten door te bellen wacht() op deze draad
    • Maar wanneer het is waar, we wisselen de status, stellen ons bericht in en bellen informerenAll () om andere threads te wekken om aan te geven dat er een belangrijke gebeurtenis heeft plaatsgevonden en ze kunnen controleren of ze kunnen doorgaan met de uitvoering
  • Evenzo is het Ontvanger zal gebruiken te ontvangen() methode:
    • Als het overdracht was ingesteld op false door Afzender, dan gaat het alleen verder, anders bellen we wacht() op deze draad
    • Wanneer aan de voorwaarde is voldaan, schakelen we de status om, stellen we alle wachtende threads op de hoogte om wakker te worden en retourneren we het datapakket dat was Ontvanger

5.1. Waarom insluiten wacht() in een terwijl Lus?

Sinds melden () en informerenAll () maakt willekeurig threads wakker die wachten op de monitor van dit object, het is niet altijd belangrijk dat aan de voorwaarde wordt voldaan. Soms kan het gebeuren dat de draad wordt gewekt, maar aan de conditie is nog niet voldaan.

We kunnen ook een vinkje definiëren om ons te behoeden voor onechte wakeups - waarbij een thread kan ontwaken door te wachten zonder ooit een melding te hebben ontvangen.

5.2. Waarom moeten we seinde() en te ontvangen() Methoden?

We hebben deze methoden erin geplaatst gesynchroniseerd methoden om intrinsieke vergrendelingen te bieden. Als een thread roept wacht() methode niet de inherente vergrendeling bezit, wordt er een fout gegenereerd.

We gaan nu creëren Afzender en Ontvanger en implementeer het Runnable interface op beide zodat hun instanties kunnen worden uitgevoerd door een thread.

Laten we eerst kijken hoe Afzender zal werken:

openbare klasse Sender implementeert Runnable {privégegevensgegevens; // standard constructors public void run () {String-pakketten [] = {"Eerste pakket", "Tweede pakket", "Derde pakket", "Vierde pakket", "Einde"}; for (String packet: packets) {data.send (packet); // Thread.sleep () om zware server-side verwerking na te bootsen, probeer {Thread.sleep (ThreadLocalRandom.current (). NextInt (1000, 5000)); } catch (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("Thread onderbroken", e); }}}}

Voor deze Afzender:

  • We maken een aantal willekeurige datapakketten die via het netwerk worden verzonden in pakketten [] array
  • Voor elk pakket bellen we alleen maar sturen()
  • Dan bellen we Thread.sleep () met een willekeurig interval om zware server-side verwerking na te bootsen

Laten we tot slot onze Ontvanger:

public class Receiver implementeert Runnable {private Data load; // standard constructors public void run () {for (String ontvangenMessage = load.receive ();! "End" .equals (ontvangenMessage); ontvangenMessage = load.receive ()) {System.out.println (ontvangenMessage); // ... probeer {Thread.sleep (ThreadLocalRandom.current (). nextInt (1000, 5000)); } catch (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("Thread onderbroken", e); }}}}

Hier bellen we gewoon load.receive () in de lus totdat we de laatste hebben "Einde" datapakket.

Laten we deze applicatie nu in actie zien:

public static void main (String [] args) {Data data = new Data (); Thread afzender = nieuwe Thread (nieuwe afzender (data)); Thread-ontvanger = nieuwe Thread (nieuwe ontvanger (data)); afzender.start (); receiver.start (); }

We krijgen de volgende output:

Eerste pakket Tweede pakket Derde pakket Vierde pakket 

En hier zijn we - we hebben alle datapakketten in de juiste volgorde ontvangen en met succes de juiste communicatie tot stand gebracht tussen onze afzender en ontvanger.

6. Conclusie

In dit artikel hebben we enkele kernconcepten voor synchronisatie in Java besproken; meer specifiek hebben we ons gericht op hoe we kunnen gebruiken wacht() en melden () om interessante synchronisatieproblemen op te lossen. En tot slot hebben we een codevoorbeeld doorgenomen waarin we deze concepten in de praktijk hebben toegepast.

Voordat we hier eindigen, is het de moeite waard te vermelden dat al deze low-level API's, zoals wacht(), melden () en informerenAll () - zijn traditionele methoden die goed werken, maar mechanismen op een hoger niveau zijn vaak eenvoudiger en beter - zoals Java's native Slot en Staat interfaces (beschikbaar in java.util.concurrent.locks pakket).

Voor meer informatie over de java.util.concurrent pakket, bezoek ons ​​overzicht van het artikel java.util.concurrent, en Slot en Staat worden behandeld in de gids voor java.util.concurrent.Locks, hier.

Zoals altijd zijn de volledige codefragmenten die in dit artikel worden gebruikt, beschikbaar op GitHub.


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