CodeGym /Java blogg /Slumpmässig /Hantera trådar. Det flyktiga nyckelordet och metoden yiel...
John Squirrels
Nivå
San Francisco

Hantera trådar. Det flyktiga nyckelordet och metoden yield().

Publicerad i gruppen
Hej! Vi fortsätter vår studie av multithreading. Idag ska vi lära känna volatilenyckelordet och yield()metoden. Låt oss dyka in :)

Det flyktiga nyckelordet

När vi skapar flertrådade applikationer kan vi stöta på två allvarliga problem. För det första, när en flertrådad applikation körs, kan olika trådar cachelagra värdena för variabler (vi har redan pratat om detta i lektionen med titeln 'Använda volatile' ) . Du kan ha situationen där en tråd ändrar värdet på en variabel, men en andra tråd ser inte ändringen, eftersom den arbetar med sin cachade kopia av variabeln. Naturligtvis kan konsekvenserna bli allvarliga. Anta att det inte är vilken gammal variabel som helst utan snarare ditt bankkontosaldo, som plötsligt börjar slumpmässigt hoppa upp och ner :) Det låter inte kul, eller hur? För det andra, i Java, operationer för att läsa och skriva alla primitiva typer,longdouble, är atomära. Tja, om du till exempel ändrar värdet på en intvariabel på en tråd, och på en annan tråd läser du värdet på variabeln, får du antingen dess gamla värde eller det nya, dvs värdet som blev resultatet av förändringen i tråd 1. Det finns inga 'mellanvärden'. Detta fungerar dock inte med longs och doubles. Varför? På grund av plattformsoberoende stöd. Kommer du ihåg att vi på de inledande nivåerna sa att Javas vägledande princip är "skriv en gång, kör var som helst"? Det betyder plattformsoberoende stöd. Med andra ord, en Java-applikation körs på alla möjliga olika plattformar. Till exempel på Windows-operativsystem, olika versioner av Linux eller MacOS. Det kommer att fungera utan problem på dem alla. Med en vikt på 64 bitar,longdoubleär de "tyngsta" primitiva i Java. Och vissa 32-bitars plattformar implementerar helt enkelt inte atomär läsning och skrivning av 64-bitars variabler. Sådana variabler läses och skrivs i två operationer. Först skrivs de första 32 bitarna till variabeln och sedan skrivs ytterligare 32 bitar. Som ett resultat kan ett problem uppstå. En tråd skriver något 64-bitars värde till en Xvariabel och gör det i två operationer. Samtidigt försöker en andra tråd läsa värdet på variabeln och gör det mellan dessa två operationer - när de första 32 bitarna har skrivits, men de andra 32 bitarna inte har skrivits. Som ett resultat läser den ett mellanliggande, felaktigt värde, och vi har en bugg. Till exempel, om vi på en sådan plattform försöker skriva numret till en 9223372036854775809 till en variabel kommer den att uppta 64 bitar. I binär form ser det ut så här: 10000000000000000000000000000000000000000000000000000000000000000000000000000001 Den första tråden börjar skriva numret till variabeln. Först skriver den de första 32 bitarna (100000000000000000000000000000000) och sedan de andra 32 bitarna (0000000000000000000000000000000001) Och den andra tråden kan fastna mellan dessa operationer, läser variabelns mellanvärde (1000000000000000000000000), som är de första 32 bitarna som redan har skrivits. I decimalsystemet är detta nummer 2 147 483 648. Med andra ord, vi ville bara skriva numret 9223372036854775809 till en variabel, men på grund av det faktum att denna operation inte är atomär på vissa plattformar, har vi det onda numret 2,147,483,648, som kom från ingenstans och kommer att ha en okänd effekt. program. Den andra tråden läste helt enkelt värdet på variabeln innan den var färdigskriven, dvs tråden såg de första 32 bitarna, men inte de andra 32 bitarna. Dessa problem uppstod naturligtvis inte igår. Java löser dem med ett enda nyckelord: volatile. Om vi ​​användervolatilenyckelord när du deklarerar någon variabel i vårt program...

public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…det betyder att:
  1. Den kommer alltid att läsas och skrivas atomärt. Även om det är en 64-bitars doubleeller long.
  2. Java-maskinen cachelagrar den inte. Så du kommer inte ha en situation där 10 trådar arbetar med sina egna lokala kopior.
Således löses två mycket allvarliga problem med bara ett ord :)

Metoden yield().

Vi har redan granskat många av Threadklassens metoder, men det finns en viktig som kommer att vara ny för dig. Det är yield()metoden . Och den gör precis vad namnet antyder! Hantera trådar.  Det flyktiga nyckelordet och metoden yield() - 2När vi kallar yieldmetoden på en tråd, pratar den faktiskt med de andra trådarna: 'Hej, killar. Jag har inte särskilt bråttom att åka någonstans, så om det är viktigt för någon av er att få processortid, ta det — jag kan vänta. Här är ett enkelt exempel på hur detta fungerar:

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 skapar och startar tre trådar sekventiellt: , Thread-0, Thread-1och Thread-2. Thread-0börjar först och ger genast efter för de andra. Sedan Thread-1startas och ger också efter. Sedan Thread-2sätts igång, vilket också ger. Vi har inga fler trådar, och efter att Thread-2ha gett sin plats sist säger trådschemaläggaren: 'Hmm, det finns inga fler nya trådar. Vilka har vi i kön? Vem gav sin plats innan Thread-2? Det verkar som det var Thread-1. Okej, det betyder att vi låter det köra. Thread-1avslutar sitt arbete och sedan fortsätter trådschemaläggaren sin koordinering: 'Okej, Thread-1klar. Har vi någon annan i kön?'. Tråd-0 är i kön: den gav sin plats precis innanThread-1. Det blir nu sin tur och kör till slut. Sedan avslutar schemaläggaren koordineringen av trådarna: 'Okej, , Thread-2du gav efter för andra trådar, och de är alla klara nu. Du var den sista som gav efter, så nu är det din tur'. Går sedan Thread-2till slut. Konsolutgången kommer att se ut så här: Tråd-0 ger sin plats till andra Tråd-1 ger sin plats till andra. Tråd-2 ger sin plats till andra. Tråd-1 har avslutats. Tråd-0 har avslutats. Tråd-2 har slutförts. Naturligtvis kan trådschemaläggaren starta trådarna i en annan ordning (till exempel 2-1-0 istället för 0-1-2), men principen förblir densamma.

Händer-före-regler

Det sista vi kommer att beröra idag är begreppet " händer före ". Som du redan vet utför trådschemaläggaren i Java huvuddelen av det arbete som är involverat i att allokera tid och resurser till trådar för att utföra sina uppgifter. Du har också upprepade gånger sett hur trådar körs i en slumpmässig ordning som vanligtvis är omöjlig att förutsäga. Och i allmänhet, efter den "sekventiella" programmeringen vi gjorde tidigare, ser flertrådsprogrammering ut som något slumpmässigt. Du har redan kommit att tro att du kan använda en mängd metoder för att kontrollera flödet av ett flertrådigt program. Men multithreading i Java har ytterligare en pelare - de fyra " händer-före "-reglerna. Att förstå dessa regler är ganska enkelt. Föreställ dig att vi har två trådar — AochB. Var och en av dessa trådar kan utföra operationer 1och 2. I varje regel, när vi säger ' A händer-före B ', menar vi att alla ändringar som gjorts av tråden Aföre operation 1och ändringarna som är resultatet av denna operation är synliga för tråden Bnär operationen 2utförs och därefter. 2Varje regel garanterar att när du skriver ett flertrådigt program kommer vissa händelser att inträffa före andra 100% av tiden, och att tråden vid operationstillfället Balltid är medveten om de ändringar som tråden Agjorde under drift 1. Låt oss granska dem.

Regel 1.

Att släppa en mutex sker innan samma bildskärm förvärvas av en annan tråd. Jag tror att du förstår allt här. Om ett objekts eller klass mutex förvärvas av en tråd., till exempel av tråd , kan Aen annan tråd (tråd ) Binte förvärva den samtidigt. Det måste vänta tills mutex släpps.

Regel 2.

Metoden händer tidigareThread.start() . Återigen, inget svårt här. Du vet redan att för att börja köra koden i metoden måste du anropa metoden på tråden. Närmare bestämt startmetoden, inte själva metoden! Denna regel säkerställer att värdena för alla variabler som ställts in innan anropas kommer att vara synliga i metoden när den börjar. Thread.run()run()start()run()Thread.start()run()

Regel 3.

Slutet på run()metoden sker innanjoin() metoden återvänder . Låt oss återgå till våra två trådar: Aoch B. Vi kallar join()metoden så att tråden Bgaranterat väntar på att tråden är klar Ainnan den gör sitt jobb. Det betyder att A-objektets run()metod garanterat kommer att löpa till slutet. Och alla ändringar av data som sker i run()trådmetoden Aär garanterat hundra procent synliga i tråden Bnär den är klar och väntar på att tråden Aska avsluta sitt arbete så att den kan börja sitt eget arbete.

Regel 4.

Att skriva till en volatilevariabel sker innan man läser från samma variabel. När vi använder volatilesökordet får vi faktiskt alltid det aktuella värdet. Även med ett longeller double(vi pratade tidigare om problem som kan hända här). Som du redan förstår är ändringar som gjorts i vissa trådar inte alltid synliga för andra trådar. Men det finns förstås väldigt frekventa situationer där sådant beteende inte passar oss. Antag att vi tilldelar ett värde till en variabel på tråden A:

int z;

….

z = 555;
Om vår Btråd skulle visa variabelns värde zpå konsolen kan den lätt visa 0, eftersom den inte känner till det tilldelade värdet. Men regel 4 garanterar att om vi deklarerar zvariabeln som volatile, kommer ändringar av dess värde på en tråd alltid att vara synliga på en annan tråd. Om vi ​​lägger till ordet volatiletill föregående kod...

volatile int z;

….

z = 555;
...då förhindrar vi situationen där tråd Bkan visa 0. Att skriva till volatilevariabler sker innan man läser från dem.
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION