CodeGym/Java-blogg/Tilfeldig/Administrere trÄder. Det flyktige sÞkeordet og yield()-me...
John Squirrels
NivÄ
San Francisco

Administrere trÄder. Det flyktige sÞkeordet og yield()-metoden

Publisert i gruppen
Hei! Vi fortsetter studiet av multithreading. I dag skal vi bli kjent med nĂžkkelordet volatileog yield()metoden. La oss dykke inn :)

Det flyktige nĂžkkelordet

NÄr vi lager flertrÄdsapplikasjoner, kan vi stÞte pÄ to alvorlige problemer. For det fÞrste, nÄr en flertrÄdsapplikasjon kjÞrer, kan forskjellige trÄder cache verdiene til variabler (vi har allerede snakket om dette i leksjonen med tittelen 'Bruke flyktig' ). Du kan ha situasjonen der en trÄd endrer verdien til en variabel, men en andre trÄd ser ikke endringen, fordi den jobber med sin hurtigbufrede kopi av variabelen. Naturligvis kan konsekvensene vÊre alvorlige. Anta at det ikke er en hvilken som helst gammel variabel, men heller bankkontosaldoen din, som plutselig begynner Ä hoppe opp og ned tilfeldig :) Det hÞres ikke gÞy ut, ikke sant? For det andre, i Java, operasjoner for Ä lese og skrive alle primitive typer,longdouble, er atomare. Vel, for eksempel, hvis du endrer verdien av en intvariabel pÄ en trÄd, og pÄ en annen trÄd leser du verdien av variabelen, fÄr du enten den gamle verdien eller den nye, dvs. verdien som ble resultatet av endringen i trÄd 1. Det er ingen 'mellomverdier'. Dette fungerer imidlertid ikke med longs og doubles. Hvorfor? PÄ grunn av stÞtte pÄ tvers av plattformer. Husker du pÄ startnivÄene at vi sa at Javas ledende prinsipp er "skriv én gang, lÞp hvor som helst"? Det betyr stÞtte pÄ tvers av plattformer. Med andre ord, en Java-applikasjon kjÞrer pÄ alle mulige forskjellige plattformer. For eksempel pÄ Windows-operativsystemer, forskjellige versjoner av Linux eller MacOS. Den vil kjÞre uten problemer pÄ dem alle. Veier inn en 64 bits,longdoubleer de 'tyngste' primitivene i Java. Og visse 32-biters plattformer implementerer ganske enkelt ikke atomlesing og skriving av 64-bits variabler. Slike variabler leses og skrives i to operasjoner. FÞrst skrives de fÞrste 32 bitene til variabelen, og deretter skrives ytterligere 32 biter. Som et resultat kan det oppstÄ et problem. En trÄd skriver en 64-bits verdi til en Xvariabel og gjÞr det i to operasjoner. Samtidig prÞver en andre trÄd Ä lese verdien av variabelen og gjÞr det mellom disse to operasjonene - nÄr de fÞrste 32 bitene er skrevet, men de andre 32 bitene ikke har det. Som et resultat leser den en mellomliggende, feil verdi, og vi har en feil. For eksempel, hvis vi pÄ en slik plattform prÞver Ä skrive nummeret til en 9223372036854775809 til en variabel, vil den oppta 64 biter. I binÊr form ser det slik ut: 10000000000000000000000000000000000000000000000000000000000000000000000000000001. Den fÞrste trÄden begynner Ä skrive nummeret til variabelen. FÞrst skriver den de fÞrste 32 bitene (100000000000000000000000000000000) og deretter de andre 32 bitene (000000000000000000000000000000001) Og den andre trÄden kan bli kilt mellom disse operasjonene, ved Ä lese variabelens mellomverdi (1000000000000000000000000), som er de fÞrste 32 bitene som allerede er skrevet. I desimalsystemet er dette tallet 2.147.483.648. Med andre ord, vi ville bare skrive tallet 9223372036854775809 til en variabel, men pÄ grunn av det faktum at denne operasjonen ikke er atomÊr pÄ noen plattformer, har vi det onde tallet 2,147,483,648, som kom ut av ingenting og vil ha en ukjent effekt. program. Den andre trÄden leste ganske enkelt verdien til variabelen fÞr den var ferdig skrevet, dvs. trÄden sÄ de fÞrste 32 bitene, men ikke de andre 32 bitene. Disse problemene oppsto selvfÞlgelig ikke i gÄr. Java lÞser dem med et enkelt nÞkkelord: volatile. Hvis vi brukervolatilenÞkkelord nÄr du erklÊrer en variabel i programmet vÄrt...
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}

det betyr at:
  1. Den vil alltid bli lest og skrevet atomĂŠrt. Selv om det er en 64-bit doubleeller long.
  2. Java-maskinen vil ikke bufre den. SÄ du vil ikke ha en situasjon der 10 trÄder jobber med sine egne lokale kopier.
Dermed lĂžses to svĂŠrt alvorlige problemer med bare ett ord :)

yield()-metoden

Vi har allerede gjennomgĂ„tt mange av Threadklassens metoder, men det er en viktig som vil vĂŠre ny for deg. Det er yield()metoden . Og den gjĂžr akkurat det navnet tilsier! Administrere trĂ„der.  Det flyktige sĂžkeordet og yield()-metoden - 2NĂ„r vi kaller yieldmetoden pĂ„ en trĂ„d, snakker den faktisk til de andre trĂ„dene: 'Hei, folkens. Jeg har ikke noe sĂŠrlig hastverk med Ă„ dra noe sted, sĂ„ hvis det er viktig for noen av dere Ă„ fĂ„ prosessortid, ta det – jeg kan vente. Her er et enkelt eksempel pĂ„ hvordan dette fungerer:
public class ThreadExample extends Thread {

   public ThreadExample() {
       this.start();
   }

   public void run() {

       System.out.println(Thread.currentThread().getName() + " yields its place to others");
       Thread.yield();
       System.out.println(Thread.currentThread().getName() + " has finished executing.");
   }

   public static void main(String[] args) {
       new ThreadExample();
       new ThreadExample();
       new ThreadExample();
   }
}
Vi oppretter og starter tre trÄder sekvensielt: Thread-0, Thread-1, og Thread-2. Thread-0starter fÞrst og gir umiddelbart etter for de andre. SÄ Thread-1er startet og gir ogsÄ. Deretter Thread-2settes i gang, som ogsÄ gir etter. Vi har ikke flere trÄder, og etter Ä Thread-2ha gitt sin plass sist, sier trÄdplanleggeren: 'Hmm, det er ikke flere nye trÄder. Hvem har vi i kÞen? Hvem ga sin plass fÞr Thread-2? Det ser ut til at det var det Thread-1. Ok, det betyr at vi lar det gÄ. Thread-1fullfÞrer arbeidet og sÄ fortsetter trÄdplanleggeren sin koordinering: 'Ok, Thread-1ferdig. Har vi noen andre i kÞen?'. TrÄd-0 er i kÞen: den ga sin plass rett fÞrThread-1. Det kommer nÄ sin tur og kjÞrer til ferdigstillelse. SÄ fullfÞrer planleggeren Ä koordinere trÄdene: 'Ok, Thread-2, du ga etter for andre trÄder, og de er alle ferdige nÄ. Du var den siste som ga etter, sÄ nÄ er det din tur. Deretter Thread-2lÞper til ferdigstillelse. Konsullutgangen vil se slik ut: TrÄd-0 gir sin plass til andre. TrÄd-1 gir sin plass til andre. TrÄd-2 gir sin plass til andre. TrÄd-1 er fullfÞrt. TrÄd-0 er fullfÞrt. TrÄd-2 er fullfÞrt. SelvfÞlgelig kan trÄdplanleggeren starte trÄdene i en annen rekkefÞlge (for eksempel 2-1-0 i stedet for 0-1-2), men prinsippet forblir det samme.

Skjer-fĂžr-regler

Det siste vi skal berÞre i dag er konseptet " skjer fÞr ". Som du allerede vet, i Java utfÞrer trÄdplanleggeren hoveddelen av arbeidet som er involvert i Ä tildele tid og ressurser til trÄder for Ä utfÞre oppgavene deres. Du har ogsÄ gjentatte ganger sett hvordan trÄder blir utfÞrt i en tilfeldig rekkefÞlge som vanligvis er umulig Ä forutsi. Og generelt, etter den "sekvensielle" programmeringen vi gjorde tidligere, ser flertrÄdsprogrammering ut som noe tilfeldig. Du har allerede kommet til Ä tro at du kan bruke en rekke metoder for Ä kontrollere flyten til et flertrÄds program. Men multithreading i Java har en pilar til - de 4 ' skjer-fÞr' -reglene. Det er ganske enkelt Ä forstÄ disse reglene. Tenk deg at vi har to trÄder - AogB. Hver av disse trÄdene kan utfÞre operasjoner 1og 2. I hver regel, nÄr vi sier ' A skjer-fÞr B ', mener vi at alle endringer gjort av trÄden AfÞr operasjon 1og endringene som fÞlge av denne operasjonen er synlige for trÄden BnÄr operasjonen 2utfÞres og deretter. 2Hver regel garanterer at nÄr du skriver et flertrÄds program, vil visse hendelser forekomme fÞr andre 100 % av tiden, og at trÄden pÄ driftstidspunktet Balltid vil vÊre klar over endringene som trÄden Agjorde under operasjonen 1. La oss vurdere dem.

Regel 1.

FrigjÞring av en mutex skjer fÞr den samme skjermen er innhentet av en annen trÄd. Jeg tror du forstÄr alt her. Hvis et objekts eller klasses mutex er hentet av én trÄd., for eksempel av trÄd , kan ikke Aen annen trÄd (thread B) hente den samtidig. Den mÄ vente til mutexen slippes.

Regel 2.

Metoden Thread.start()skjer fÞr Thread.run() . Igjen, ikke noe vanskelig her. Du vet allerede at for Ä begynne Ä kjÞre koden inne i run()metoden, mÄ du kalle start()metoden pÄ trÄden. NÊrmere bestemt startmetoden, ikke run()selve metoden! Denne regelen sikrer at verdiene til alle variabler satt fÞr Thread.start()kalles vil vÊre synlige inne i run()metoden nÄr den begynner.

Regel 3.

Slutten av run()metoden skjer fÞr returen fra join()metoden. La oss gÄ tilbake til vÄre to trÄder: Aog B. Vi kaller join()metoden slik at trÄden Bgarantert venter pÄ at trÄden er ferdig AfÞr den gjÞr jobben sin. Dette betyr at A-objektets run()metode garantert vil lÞpe helt til slutten. Og alle endringer i data som skjer i run()trÄdmetoden Aer hundre prosent garantert synlige i trÄden BnÄr den er ferdig og venter pÄ at trÄden Askal fullfÞre arbeidet slik at den kan begynne sitt eget arbeid.

Regel 4.

Å skrive til en volatilevariabel skjer fĂžr lesing fra den samme variabelen. NĂ„r vi bruker sĂžkeordet volatile, fĂ„r vi faktisk alltid gjeldende verdi. Selv med et longeller double(vi snakket tidligere om problemer som kan skje her). Som du allerede forstĂ„r, er endringer som er gjort pĂ„ enkelte trĂ„der ikke alltid synlige for andre trĂ„der. Men det er selvfĂžlgelig veldig hyppige situasjoner der slik oppfĂžrsel ikke passer oss. Anta at vi tilordner en verdi til en variabel pĂ„ trĂ„den A:
int z;


.

z = 555;
Hvis BtrÄden vÄr skulle vise verdien til zvariabelen pÄ konsollen, kan den lett vise 0, fordi den ikke vet om den tildelte verdien. Men regel 4 garanterer at hvis vi erklÊrer zvariabelen som volatile, vil endringer i verdien pÄ én trÄd alltid vÊre synlige pÄ en annen trÄd. Hvis vi legger til ordet volatiletil forrige kode...
volatile int z;


.

z = 555;
...sÄ forhindrer vi situasjonen der trÄd Bkan vise 0. Skriving til volatilevariabler skjer fÞr du leser fra dem.
Kommentarer
  • PopulĂŠr
  • Ny
  • Gammel
Du mÄ vÊre pÄlogget for Ä legge igjen en kommentar
Denne siden har ingen kommentarer ennÄ