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
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION