CodeGym /Java Blog /Willekeurig /Draden beheren. Het vluchtige trefwoord en de methode yie...
John Squirrels
Niveau 41
San Francisco

Draden beheren. Het vluchtige trefwoord en de methode yield().

Gepubliceerd in de groep Willekeurig
Hoi! We vervolgen onze studie van multithreading. Vandaag leren we het volatiletrefwoord en de yield()methode kennen. Laten we erin duiken :)

Het vluchtige zoekwoord

Bij het maken van multithreaded applicaties kunnen we twee serieuze problemen tegenkomen. Ten eerste, wanneer een multithreaded applicatie draait, kunnen verschillende threads de waarden van variabelen cachen (we hebben het er al over gehad in de les getiteld 'Volatile gebruiken' ). U kunt de situatie hebben waarin één thread de waarde van een variabele verandert, maar een tweede thread ziet de wijziging niet, omdat deze werkt met de in de cache opgeslagen kopie van de variabele. Uiteraard kunnen de gevolgen ernstig zijn. Stel dat het niet zomaar een variabele is, maar je bankrekeningsaldo, dat plotseling willekeurig op en neer begint te springen :) Dat klinkt niet leuk, toch? Ten tweede, in Java, bewerkingen voor het lezen en schrijven van alle primitieve typen,longdouble, zijn atomair. Als je bijvoorbeeld de waarde van een intvariabele op de ene thread verandert, en op een andere thread lees je de waarde van de variabele, dan krijg je de oude waarde of de nieuwe, dat wil zeggen de waarde die het gevolg is van de wijziging in thread 1. Er zijn geen 'tussenliggende waarden'. Dit werkt echter niet met longs en doubles. Waarom? Vanwege platformonafhankelijke ondersteuning. Weet je nog dat we op de beginniveaus zeiden dat het leidende principe van Java 'één keer schrijven, overal uitvoeren' is? Dat betekent platformonafhankelijke ondersteuning. Met andere woorden, een Java-applicatie draait op allerlei verschillende platformen. Bijvoorbeeld op Windows-besturingssystemen, verschillende versies van Linux of MacOS. Het zal allemaal zonder problemen verlopen. Met een gewicht van 64 bits,longdoublezijn de 'zwaarste' primitieven op Java. En bepaalde 32-bits platforms implementeren eenvoudigweg geen atomisch lezen en schrijven van 64-bits variabelen. Dergelijke variabelen worden in twee bewerkingen gelezen en geschreven. Eerst worden de eerste 32 bits naar de variabele geschreven en vervolgens worden nog eens 32 bits geschreven. Hierdoor kan er een probleem ontstaan. Eén thread schrijft een waarde van 64 bits naar een Xvariabele en doet dit in twee bewerkingen. Tegelijkertijd probeert een tweede thread de waarde van de variabele te lezen en doet dit tussen deze twee bewerkingen in - wanneer de eerste 32 bits zijn geschreven, maar de tweede 32 bits niet. Als gevolg hiervan leest het een tussenliggende, onjuiste waarde en hebben we een bug. Als we op zo'n platform bijvoorbeeld proberen het nummer naar een 9223372036854775809 te schrijven naar een variabele, zal het 64 bits in beslag nemen. In binaire vorm ziet het er zo uit: 100000000000000000000000000000000000000000000000000000000000001 De eerste thread begint het nummer naar de variabele te schrijven. Eerst schrijft het de eerste 32 bits (1000000000000000000000000000000) en vervolgens de tweede 32 bits (0000000000000000000000000000001) En de tweede thread kan klem komen te zitten tussen deze bewerkingen, waarbij de tussenwaarde van de variabele wordt gelezen (10000000000000000000000000000000), dit zijn de eerste 32 bits die al zijn geschreven. In het decimale stelsel is dit getal 2.147.483.648. Met andere woorden, we wilden gewoon het nummer 9223372036854775809 naar een variabele schrijven, maar vanwege het feit dat deze bewerking op sommige platforms niet atomair is, hebben we het kwaadaardige nummer 2.147.483.648, dat uit het niets kwam en een onbekend effect zal hebben op de programma. De tweede thread las gewoon de waarde van de variabele voordat deze klaar was met schrijven, dwz de thread zag de eerste 32 bits, maar niet de tweede 32 bits. Natuurlijk zijn deze problemen gisteren niet ontstaan. Java lost ze op met één trefwoord: volatile. Als we devolatiletrefwoord bij het declareren van een variabele in ons programma...

public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…het betekent dat:
  1. Het zal altijd atomair worden gelezen en geschreven. Zelfs als het een 64-bits doubleof long.
  2. De Java-machine zal het niet cachen. U zult dus geen situatie hebben waarin 10 threads werken met hun eigen lokale kopieën.
Zo worden twee zeer ernstige problemen opgelost met slechts één woord :)

De opbrengst() methode

We hebben al veel van de Threadmethoden van de klas besproken, maar er is een belangrijke die nieuw voor je zal zijn. Het is de yield()methode . En het doet precies wat de naam aangeeft! Wanneer we de methode op een thread Draden beheren.  Het vluchtige trefwoord en de methode yield() - 2aanroepen , praat deze eigenlijk met de andere threads: 'Hé, jongens. yieldIk heb geen bijzondere haast om ergens heen te gaan, dus als het voor een van jullie belangrijk is om processortijd te krijgen, neem die dan - ik kan wachten'. Hier is een eenvoudig voorbeeld van hoe dit werkt:

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();
   }
}
We maken en starten achtereenvolgens drie threads: Thread-0, Thread-1, en Thread-2. Thread-0begint als eerste en geeft onmiddellijk toe aan de anderen. Dan Thread-1wordt gestart en levert ook op. Dan Thread-2wordt gestart, wat ook nog eens oplevert. We hebben geen threads meer en nadat hij Thread-2als laatste zijn plaats heeft opgegeven, zegt de threadplanner: 'Hmm, er zijn geen nieuwe threads meer. Wie hebben we in de wachtrij? Wie heeft eerder zijn plaats afgestaan Thread-2? Het lijkt erop dat het zo was Thread-1. Oké, dat betekent dat we het laten lopen'. Thread-1voltooit zijn werk en vervolgens vervolgt de threadplanner zijn coördinatie: 'Oké, Thread-1klaar. Staat er nog iemand in de rij?'. Thread-0 staat in de wachtrij: het heeft vlak ervoor zijn plaats opgegevenThread-1. Het komt nu aan de beurt en loopt tot voltooiing. Dan is de planner klaar met het coördineren van de threads: 'Oké, Thread-2je hebt toegegeven aan andere threads en ze zijn nu allemaal klaar. Jij gaf als laatste toe, dus nu is het jouw beurt'. Loopt dan Thread-2tot voltooiing. De console-uitvoer ziet er als volgt uit: Thread-0 geeft zijn plaats af aan anderen Thread-1 geeft zijn plaats af aan anderen Thread-2 geeft zijn plaats af aan anderen Thread-1 is klaar met uitvoeren. Thread-0 is klaar met uitvoeren. Thread-2 is klaar met uitvoeren. Natuurlijk kan de threadplanner de threads in een andere volgorde starten (bijvoorbeeld 2-1-0 in plaats van 0-1-2), maar het principe blijft hetzelfde.

Gebeurt vóór regels

Het laatste waar we het vandaag over zullen hebben, is het concept van ' gebeurt eerder '. Zoals u al weet, voert de threadplanner in Java het grootste deel van het werk uit dat komt kijken bij het toewijzen van tijd en middelen aan threads om hun taken uit te voeren. Je hebt ook herhaaldelijk gezien hoe threads worden uitgevoerd in een willekeurige volgorde die meestal niet te voorspellen is. En in het algemeen, na de 'sequentiële' programmering die we eerder deden, ziet multithreaded programmeren eruit als iets willekeurigs. U bent al gaan geloven dat u een groot aantal methoden kunt gebruiken om de stroom van een multithreaded programma te beheersen. Maar multithreading in Java heeft nog een pijler: de 4 ' gebeurten-voor' -regels. Het begrijpen van deze regels is vrij eenvoudig. Stel je voor dat we twee threads hebben - AenB. Elk van deze threads kan bewerkingen uitvoeren 1en 2. Als we in elke regel zeggen ' A gebeurt vóór B ', bedoelen we dat alle wijzigingen die Avóór de bewerking door de thread zijn aangebracht 1en de wijzigingen die het resultaat zijn van deze bewerking, zichtbaar zijn voor de thread Bwanneer de bewerking 2wordt uitgevoerd en daarna. Elke regel garandeert dat wanneer u een multithreaded programma schrijft, bepaalde gebeurtenissen 100% van de tijd vóór andere zullen plaatsvinden, en dat op het moment van operatie 2thread Baltijd op de hoogte zal zijn van de wijzigingen die thread Atijdens operatie heeft aangebracht 1. Laten we ze bekijken.

Regel 1.

Het vrijgeven van een mutex gebeurt voordat dezelfde monitor wordt overgenomen door een andere thread. Ik denk dat je hier alles begrijpt. Als de mutex van een object of klasse door één thread wordt verkregen, bijvoorbeeld door thread , kan Aeen andere thread (thread B) deze niet tegelijkertijd verkrijgen. Het moet wachten tot de mutex wordt vrijgegeven.

Regel 2.

De Thread.start()methode gebeurt eerder Thread.run() . Nogmaals, niets moeilijks hier. U weet al dat u, om de code binnen de methode uit te voeren , de methode op de thread run()moet aanroepen . start()Met name de startmethode, niet de run()methode zelf! Deze regel zorgt ervoor dat de waarden van alle variabelen die zijn ingesteld voordat Thread.start()wordt aangeroepen, zichtbaar zijn in de run()methode zodra deze begint.

Regel 3.

Het einde van de run()methode vindt plaats vóór de terugkeer van de join()methode. Laten we terugkeren naar onze twee threads: Aen B. We noemen de join()methode zodat thread Bgegarandeerd wacht op de voltooiing van thread Avoordat deze zijn werk doet. Dit betekent dat de methode van het A-object run()gegarandeerd tot het einde wordt uitgevoerd. En alle wijzigingen in gegevens die plaatsvinden in de run()methode van thread Azijn voor honderd procent gegarandeerd zichtbaar in thread Bzodra het klaar is, wachtend tot thread Azijn werk voltooit, zodat het zijn eigen werk kan beginnen.

Regel 4.

Het schrijven naar een volatilevariabele gebeurt vóór het lezen van diezelfde variabele. Als we het trefwoord gebruiken volatile, krijgen we eigenlijk altijd de huidige waarde. Zelfs met een longof double(we spraken eerder over problemen die hier kunnen voorkomen). Zoals je al begrijpt, zijn wijzigingen in sommige threads niet altijd zichtbaar voor andere threads. Maar er zijn natuurlijk heel vaak situaties waarin dergelijk gedrag niet bij ons past. Stel dat we een waarde toekennen aan een variabele op thread A:

int z;

….

z = 555;
Als onze Bthread de waarde van de zvariabele op de console zou weergeven, zou deze gemakkelijk 0 kunnen weergeven, omdat deze de toegewezen waarde niet kent. Maar regel 4 garandeert dat als we de zvariabele declareren als volatile, wijzigingen in de waarde ervan op de ene thread altijd zichtbaar zullen zijn op een andere thread. Als we het woord toevoegen volatileaan de vorige code...

volatile int z;

….

z = 555;
... dan voorkomen we de situatie waarin thread B0 zou kunnen weergeven. Schrijven naar volatilevariabelen gebeurt voordat ze worden gelezen.
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION