vent og varsle () Metoder i Java

1. Introduksjon

I denne artikkelen ser vi på en av de mest grunnleggende mekanismene i Java - trådsynkronisering.

Vi vil først diskutere noen viktige samtidige termer og metoder.

Og vi skal utvikle en enkel applikasjon - der vi skal håndtere problemer med samtidighet, med målet om bedre forståelse vente() og gi beskjed().

2. Trådsynkronisering i Java

I et flertrådet miljø kan flere tråder prøve å endre den samme ressursen. Hvis tråder ikke håndteres riktig, vil dette selvfølgelig føre til konsistensproblemer.

2.1. Beskyttede blokker i Java

Et verktøy vi kan bruke til å koordinere handlinger av flere tråder i Java - er beskyttede blokker. Slike blokker kontrollerer en bestemt tilstand før de fortsetter utførelsen.

Med det i bakhodet vil vi bruke:

  • Object.wait () - å avbryte en tråd
  • Object.notify () - å vekke en tråd opp

Dette kan forstås bedre fra følgende diagram som viser livssyklusen til en Tråd:

Vær oppmerksom på at det er mange måter å kontrollere denne livssyklusen på; i denne artikkelen skal vi imidlertid bare fokusere på vente() og gi beskjed().

3. Den vente() Metode

Enkelt sagt når vi ringer vente() - dette tvinger den gjeldende tråden til å vente til en annen tråd påkaller gi beskjed() eller notifyAll () på samme objekt.

For dette må den gjeldende tråden eie objektets skjerm. I følge Javadocs kan dette skje når:

  • vi har henrettet synkronisert forekomstmetode for det gitte objektet
  • vi har henrettet kroppen til en synkronisert blokker på det gitte objektet
  • ved å utføre synkronisert statisk metoder for gjenstander av typen Klasse

Merk at bare en aktiv tråd kan eie et objekts skjerm om gangen.

Dette vente() metoden kommer med tre overbelastede signaturer. La oss ta en titt på disse.

3.1. vente()

De vente() metoden får den gjeldende tråden til å vente på ubestemt tid til en annen tråd påberoper seg gi beskjed() for dette objektet eller notifyAll ().

3.2. vent (lang tidsavbrudd)

Ved hjelp av denne metoden kan vi spesifisere et tidsavbrudd etter hvilket tråden skal vekkes automatisk. En tråd kan vekkes før den når tidsavbruddet gi beskjed() eller notifyAll ().

Legg merke til at du ringer vent (0) er det samme som å ringe vente().

3.3. vent (lang tidsavbrudd, int nanos)

Dette er enda en signatur som gir den samme funksjonaliteten, med den eneste forskjellen at vi kan gi høyere presisjon.

Den totale tidsavbruddsperioden (i nanosekunder) beregnes som 1_000_000 * timeout + nano.

4. varsle () og notifyAll ()

De gi beskjed() metoden brukes til å vekke tråder som venter på tilgang til dette objektets skjerm.

Det er to måter å varsle ventetråder.

4.1. gi beskjed()

For alle tråder som venter på dette objektets skjerm (ved å bruke en av vente() metode), metoden gi beskjed() varsler noen av dem om å våkne vilkårlig. Valget av nøyaktig hvilken tråd du skal våkne er ikke-deterministisk og avhenger av implementeringen.

Siden gi beskjed() vekker opp en enkelt tilfeldig tråd, den kan brukes til å implementere gjensidig utelukkende låsing der tråder gjør lignende oppgaver, men i de fleste tilfeller vil det være mer levedyktig å implementere notifyAll ().

4.2. notifyAll ()

Denne metoden vekker ganske enkelt alle tråder som venter på dette objektets skjerm.

De vekket trådene vil fullføres på vanlig måte - som alle andre tråder.

Men før vi lar henrettelsen fortsette, alltid definere en rask sjekk for tilstanden som kreves for å fortsette med tråden - fordi det kan være noen situasjoner der tråden ble vekket uten å motta et varsel (dette scenariet blir diskutert senere i et eksempel).

5. Synkroniseringsproblem for avsender-mottaker

Nå som vi forstår det grunnleggende, la oss gå gjennom en enkel AvsenderMottaker applikasjon - som vil gjøre bruk av vente() og gi beskjed() metoder for å sette opp synkronisering mellom dem:

  • De Avsender skal sende en datapakke til Mottaker
  • De Mottaker kan ikke behandle datapakken før Avsender er ferdig med å sende den
  • Tilsvarende Avsender må ikke prøve å sende en annen pakke med mindre Mottaker har allerede behandlet forrige pakke

La oss først lage Data klasse som består av dataene pakke som vil bli sendt fra Avsender til Mottaker. Vi bruker vente() og notifyAll () for å sette opp synkronisering mellom dem:

offentlig klasse Data {privat strengpakke; // Sant hvis mottakeren skulle vente // Falsk hvis avsenderen skulle vente privat boolsk overføring = sann; offentlig synkronisert ugyldig sending (strengpakke) {mens (! overføring) {prøv {vent (); } fange (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("Tråden avbrutt", e); }} overføring = falsk; this.packet = pakke; notifyAll (); } offentlig synkronisert strengmottak () {mens (overføring) {prøv {vent (); } fange (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("Tråden avbrutt", e); }} overføring = sann; notifyAll (); returpakke; }}

La oss bryte ned hva som skjer her:

  • De pakke variabel betegner dataene som overføres over nettverket
  • Vi har en boolsk variabel overføring - som Avsender og Mottaker vil bruke for synkronisering:
    • Hvis denne variabelen er ekte, og så Mottaker skal vente på Avsender for å sende meldingen
    • Hvis det er falsk, deretter Avsender skal vente på Mottaker for å motta meldingen
  • De Avsender bruker sende() metode for å sende data til Mottaker:
    • Hvis overføre er falsk, vi venter med å ringe vente() på denne tråden
    • Men når det er det ekte, vi bytter status, angir meldingen og ringer notifyAll () å vekke andre tråder for å spesifisere at en betydelig hendelse har skjedd, og de kan sjekke om de kan fortsette å kjøre
  • Tilsvarende Mottaker vil bruke motta() metode:
    • Hvis den overføre ble satt til falsk av Avsender, så vil bare det fortsette, ellers ringer vi vente() på denne tråden
    • Når vilkåret er oppfylt, bytter vi statusen, varsler alle ventende tråder for å våkne og returnere datapakken som var Mottaker

5.1. Hvorfor legge ved vente() i en samtidig som Løkke?

Siden gi beskjed() og notifyAll () vekker tilfeldig tråder som venter på dette objektets skjerm, det er ikke alltid viktig at vilkåret er oppfylt. Noen ganger kan det hende at tråden er vekket, men tilstanden er faktisk ikke tilfredsstilt ennå.

Vi kan også definere en sjekk for å redde oss fra falske våkninger - der en tråd kan våkne opp fra å vente uten å ha mottatt et varsel.

5.2. Hvorfor trenger vi å synkronisere sslutt() og motta() Metoder?

Vi plasserte disse metodene inne synkronisert metoder for å gi iboende låser. Hvis en tråd ringer vente() metoden ikke eier den iboende låsen, vil en feil bli kastet.

Vi oppretter nå Avsender og Mottaker og implementere Kjørbar grensesnitt på begge slik at forekomsten kan utføres av en tråd.

La oss først se hvordan Avsender skal jobbe:

offentlig klasse Avsender implementerer Runnable {private Data data; // standard constructors public void run () {Strengpakker [] = {"Første pakke", "Andre pakke", "Tredje pakke", "Fjerde pakke", "Slutt"}; for (String pakke: pakker) {data.send (pakke); // Thread.sleep () for å etterligne tung prosessering på serversiden, prøv {Thread.sleep (ThreadLocalRandom.current (). NextInt (1000, 5000)); } fange (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("Tråden avbrutt", e); }}}}

For dette Avsender:

  • Vi lager noen tilfeldige datapakker som vil bli sendt over nettverket i pakker [] array
  • For hver pakke ringer vi bare sende()
  • Så ringer vi Thread.sleep () med tilfeldig intervall for å etterligne tung behandling på serversiden

Til slutt, la oss implementere vår Mottaker:

offentlig klasse Mottaker implementerer Runnable {private Data load; // standard constructors public void run () {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 avbrutt", e); }}}}

Her ringer vi bare load.receive () i løkken til vi får den siste "Slutt" datapakke.

La oss nå se denne applikasjonen i aksjon:

public static void main (String [] args) {Data data = new Data (); Trådsender = ny tråd (ny avsender (data)); Trådmottaker = ny tråd (ny mottaker (data)); sender.start (); receiver.start (); }

Vi mottar følgende utdata:

Første pakke Andre pakke Tredje pakke Fjerde pakke 

Og her er vi - vi har mottatt alle datapakker i riktig rekkefølge og vellykket etablert riktig kommunikasjon mellom avsender og mottaker.

6. Konklusjon

I denne artikkelen diskuterte vi noen kjernesynkroniseringskonsepter i Java; mer spesifikt, fokuserte vi på hvordan vi kan bruke vente() og gi beskjed() for å løse interessante synkroniseringsproblemer. Og til slutt gikk vi gjennom et kodeeksempel der vi anvendte disse konseptene i praksis.

Før vi avvikler her, er det verdt å nevne at alle disse API-ene på lavt nivå, for eksempel vente(), gi beskjed() og notifyAll () - er tradisjonelle metoder som fungerer bra, men mekanismer på høyere nivå er ofte enklere og bedre - som for eksempel Java Låse og Tilstand grensesnitt (tilgjengelig i java.util.concurrent.locks pakke).

For mer informasjon om java.util.concurrent pakken, besøk vår oversikt over java.util.concurrent artikkelen, og Låse og Tilstand er dekket i guiden til java.util.concurrent.Locks, her.

Som alltid er de komplette kodebitene som brukes i denne artikkelen, tilgjengelig på GitHub.


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