CodeGym /Java blog /Tilfældig /Håndtering af tråde. Det flygtige søgeord og yield() meto...
John Squirrels
Niveau
San Francisco

Håndtering af tråde. Det flygtige søgeord og yield() metoden

Udgivet i gruppen
Hej! Vi fortsætter vores undersøgelse af multithreading. I dag lærer vi nøgleordet volatileog yield()metoden at kende. Lad os dykke ned :)

Det flygtige søgeord

Når vi opretter multitrådede applikationer, kan vi løbe ind i to alvorlige problemer. For det første, når en multithreaded-applikation kører, kan forskellige tråde cache værdierne af variabler (vi har allerede talt om dette i lektionen med titlen 'Brug af volatile' ). Du kan have den situation, hvor en tråd ændrer værdien af ​​en variabel, men en anden tråd kan ikke se ændringen, fordi den arbejder med sin cachelagrede kopi af variablen. Konsekvenserne kan naturligvis være alvorlige. Antag, at det ikke er en hvilken som helst gammel variabel, men snarere din bankkontosaldo, som pludselig begynder at springe tilfældigt op og ned :) Det lyder ikke sjovt, vel? For det andet, i Java, operationer til at læse og skrive alle primitive typer,longdouble, er atomare. Tja, hvis du for eksempel ændrer værdien af ​​en intvariabel på en tråd, og på en anden tråd læser du værdien af ​​variablen, får du enten dens gamle værdi eller den nye, dvs. den værdi, der er resultatet af ændringen i tråd 1. Der er ingen 'mellemværdier'. Dette virker dog ikke med longs og doubles. Hvorfor? På grund af support på tværs af platforme. Husker du på de begyndende niveauer, at vi sagde, at Javas ledende princip er 'skriv én gang, løb hvor som helst'? Det betyder support på tværs af platforme. Med andre ord kører en Java-applikation på alle mulige forskellige platforme. For eksempel på Windows-operativsystemer, forskellige versioner af Linux eller MacOS. Det vil køre uden problemer på dem alle. Vejning i en 64 bit,longdoubleer de 'tyngste' primitiver i Java. Og visse 32-bit platforme implementerer simpelthen ikke atomlæsning og skrivning af 64-bit variabler. Sådanne variable læses og skrives i to operationer. Først skrives de første 32 bit til variablen, og derefter skrives yderligere 32 bit. Som følge heraf kan der opstå et problem. En tråd skriver en 64-bit værdi til en Xvariabel og gør det i to operationer. Samtidig forsøger en anden tråd at læse værdien af ​​variablen og gør det mellem disse to operationer - når de første 32 bit er blevet skrevet, men de anden 32 bit ikke er det. Som følge heraf læser den en mellemliggende, forkert værdi, og vi har en fejl. For eksempel, hvis vi på en sådan platform forsøger at skrive nummeret til en 9223372036854775809 til en variabel, vil den optage 64 bit. I binær form ser det ud som dette: 100000000000000000000000000000000000000000000000000000000000000000000001 Den første tråd begynder at skrive nummeret til variablen. Først skriver den de første 32 bits (10000000000000000000000000000000) og derefter de anden 32 bits (0000000000000000000000000000000001) Og den anden tråd kan komme i klemme mellem disse operationer ved at læse variablens mellemværdi (1000000000000000000000000), som er de første 32 bit, der allerede er skrevet. I decimalsystemet er dette tal 2.147.483.648. Med andre ord, vi ville bare skrive tallet 9223372036854775809 til en variabel, men på grund af det faktum, at denne operation ikke er atomær på nogle platforme, har vi det onde tal 2.147.483.648, som kom ud af ingenting og vil have en ukendt effekt. program. Den anden tråd læste blot værdien af ​​variablen, før den var færdig med at blive skrevet, dvs. tråden så de første 32 bit, men ikke de anden 32 bit. Disse problemer opstod naturligvis ikke i går. Java løser dem med et enkelt nøgleord: volatile. Hvis vi brugervolatilenøgleord, når du erklærer en variabel i vores program...

public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…det betyder at:
  1. Det vil altid blive læst og skrevet atomært. Selvom det er en 64-bit doubleeller long.
  2. Java-maskinen vil ikke cache det. Så du vil ikke have en situation, hvor 10 tråde arbejder med deres egne lokale kopier.
Således er to meget alvorlige problemer løst med kun et ord :)

yield() metoden

Vi har allerede gennemgået mange af Threadklassens metoder, men der er en vigtig, som vil være ny for dig. Det er yield()metoden . Og den gør præcis, hvad navnet antyder! Håndtering af tråde.  Det flygtige søgeord og yield()-metoden - 2Når vi kalder yieldmetoden på en tråd, taler den faktisk til de andre tråde: 'Hej, gutter. Jeg har ikke særlig travlt med at gå nogen steder, så hvis det er vigtigt for nogen af ​​jer at få processortid, så tag det - jeg kan vente'. Her er et simpelt 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 opretter og starter sekventielt tre tråde: Thread-0, Thread-1, og Thread-2. Thread-0starter først og giver straks efter for de andre. Så Thread-1startes og giver også efter. Derefter Thread-2startes, hvilket også giver efter. Vi har ikke flere tråde, og efter at Thread-2have givet sin plads sidst, siger trådplanlæggeren: 'Hmm, der er ikke flere nye tråde. Hvem har vi i køen? Hvem gav sin plads før Thread-2? Det lader til, at det var Thread-1. Okay, det betyder, at vi lader det køre'. Thread-1afslutter sit arbejde, og så fortsætter trådplanlæggeren sin koordinering: 'Okay, Thread-1færdig. Har vi andre i køen?'. Tråd-0 er i køen: den gav sin plads lige førThread-1. Det får nu sin tur og kører til afslutning. Så er planlæggeren færdig med at koordinere trådene: 'Okay, Thread-2, du gav efter for andre tråde, og de er alle færdige nu. Du var den sidste til at give efter, så nu er det din tur'. Kører derefter Thread-2til færdiggørelse. Konsoloutputtet vil se sådan ud: Tråd-0 giver sin plads til andre. Tråd-1 giver sin plads til andre. Tråd-2 giver sin plads til andre. Tråd-1 er færdig med at udføre. Tråd-0 er færdig med at udføre. Tråd-2 er færdig med at udføre. Selvfølgelig kan trådplanlæggeren starte trådene i en anden rækkefølge (for eksempel 2-1-0 i stedet for 0-1-2), men princippet forbliver det samme.

Sker-før regler

Den sidste ting, vi vil berøre i dag, er begrebet ' sker før '. Som du allerede ved, udfører trådplanlæggeren hovedparten af ​​det arbejde, der er involveret i at allokere tid og ressourcer til tråde for at udføre deres opgaver i Java. Du har også gentagne gange set, hvordan tråde udføres i en tilfældig rækkefølge, som normalt er umulig at forudsige. Og generelt, efter den 'sekventielle' programmering, vi lavede tidligere, ser flertrådsprogrammering ud som noget tilfældigt. Du er allerede kommet til at tro, at du kan bruge et væld af metoder til at kontrollere strømmen af ​​et multitrådsprogram. Men multithreading i Java har en søjle mere - de 4 ' sker-før '-regler. Det er ret simpelt at forstå disse regler. Forestil dig, at vi har to tråde - AogB. Hver af disse tråde kan udføre operationer 1og 2. I hver regel, når vi siger ' A sker-før B ', mener vi, at alle ændringer foretaget af tråd Afør operation 1og ændringerne som følge af denne operation er synlige for tråd Bnår operationen 2udføres og derefter. 2Hver regel garanterer, at når du skriver et program med flere tråde, vil visse hændelser forekomme før andre 100% af tiden, og at tråden på driftstidspunktet Baltid vil være opmærksom på de ændringer, som tråden Alavede under driften 1. Lad os gennemgå dem.

Regel 1.

Frigivelse af en mutex sker, før den samme skærm er erhvervet af en anden tråd. Jeg tror du forstår alt her. Hvis et objekts eller klasses mutex er erhvervet af en tråd., for eksempel af tråd A, kan en anden tråd (thread B) ikke erhverve den på samme tid. Det skal vente, indtil mutex'en er frigivet.

Regel 2.

Metoden sker førThread.start() . Igen, intet svært her. Du ved allerede, at for at begynde at køre koden inde i metoden, skal du kalde metoden på tråden. Konkret startmetoden, ikke selve metoden! Denne regel sikrer, at værdierne for alle variabler, der er sat før kaldet, vil være synlige i metoden, når den begynder. Thread.run()run()start()run()Thread.start()run()

Regel 3.

Slutningen af run()​​metoden sker før tilbagevenden fra join()metoden. Lad os vende tilbage til vores to tråde: Aog B. Vi kalder join()metoden, så tråden Bmed garanti venter på færdiggørelsen af ​​tråden, Afør den gør sit arbejde. Det betyder, at A-objektets run()metode med garanti løber til det sidste. Og alle ændringer af data, der sker i trådmetoden, run()er Aet hundrede procent garanteret synlige i tråden, Bnår den er færdig og venter på, at tråden Aafslutter sit arbejde, så den kan begynde sit eget arbejde.

Regel 4.

At skrive til en volatilevariabel sker før læsning fra den samme variabel. Når vi bruger volatilesøgeordet, får vi faktisk altid den aktuelle værdi. Selv med et longeller double(vi talte tidligere om problemer, der kan ske her). Som du allerede forstår, er ændringer foretaget i nogle tråde ikke altid synlige for andre tråde. Men der er selvfølgelig meget hyppige situationer, hvor sådan adfærd ikke passer os. Antag, at vi tildeler en værdi til en variabel på tråden A:

int z;

….

z = 555;
Hvis vores Btråd skulle vise værdien af z​​variablen på konsollen, kunne den nemt vise 0, fordi den ikke kender til den tildelte værdi. Men regel 4 garanterer, at hvis vi erklærer zvariablen som volatile, så vil ændringer af dens værdi på én tråd altid være synlige på en anden tråd. Hvis vi tilføjer ordet volatiletil den forrige kode...

volatile int z;

….

z = 555;
...så forhindrer vi situationen, hvor tråd Bkan vise 0. Skrivning til volatilevariabler sker, før du læser fra dem.
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION