Sprievodca po prchavých kľúčových slovách v jazyku Java

1. Prehľad

Ak chýbajú potrebné synchronizácie, kompilátor, runtime alebo procesory môžu použiť všetky druhy optimalizácií. Aj keď sú tieto optimalizácie väčšinou prospešné, niekedy môžu spôsobiť jemné problémy.

Ukladanie do pamäte a zmena poradia patria medzi tie optimalizácie, ktoré nás môžu prekvapiť v súbežných kontextoch. Java a JVM poskytujú mnoho spôsobov riadenia poradia pamäte a prchavý kľúčové slovo je jedným z nich.

V tomto článku sa zameriame na tento základný, ale často nepochopený koncept v jazyku Java - prchavý kľúčové slovo. Najprv začneme trochou pozadia o tom, ako funguje základná architektúra počítača, a potom sa oboznámime s poradím pamäte v Jave.

2. Zdieľaná architektúra viacerých procesorov

Procesory sú zodpovedné za vykonávanie programových pokynov. Preto musia z RAM načítať inštrukcie programu aj požadované údaje.

Pretože CPU sú schopné vykonávať značný počet inštrukcií za sekundu, nie je pre nich načítanie z RAM také ideálne. Na zlepšenie tejto situácie používajú procesory triky ako Vykonanie mimo poradia, Predikcia vetiev, Špekulatívne vykonávanie a samozrejme ukladanie do pamäte cache.

Tu vstupuje do hry nasledujúca hierarchia pamätí:

Pretože rôzne jadrá vykonávajú viac pokynov a manipulujú s ďalšími údajmi, zapĺňajú svoje pamäte cache relevantnejšími údajmi a pokynmi. Toto zlepší celkový výkon na úkor zavedenia výziev súdržnosti vyrovnávacej pamäte.

Zjednodušene povedané, mali by sme si dvakrát rozmyslieť, čo sa stane, keď jedno vlákno aktualizuje hodnotu uloženú v pamäti.

3. Kedy použiť prchavý

Aby sme viac rozšírili koherenciu cache, vypožičajme si jeden príklad z knihy Java Concurrency in Practice:

public class TaskRunner {private static int number; pripravený súkromný statický boolean; súkromná statická trieda Reader rozširuje vlákno {@Override public void run () {while (! ready) {Thread.yield (); } System.out.println (číslo); }} public static void main (String [] args) {new Reader (). start (); číslo = 42; pripravený = pravda; }}

The TaskRunner trieda udržuje dve jednoduché premenné. Vo svojej hlavnej metóde vytvára ďalšie vlákno, ktoré sa točí na pripravený premenná, pokiaľ je nepravdivé. Keď sa premenná stane pravda, vlákno jednoducho vytlačí číslo premenná.

Mnohí môžu očakávať, že tento program po krátkom oneskorení jednoducho vytlačí 42. V skutočnosti však môže byť oneskorenie oveľa dlhšie. Môže to dokonca visieť naveky alebo dokonca vytlačiť nulu!

Príčinou týchto anomálií je chýbajúca správna viditeľnosť pamäte a zmena poradia. Poďme ich vyhodnotiť podrobnejšie.

3.1. Viditeľnosť pamäte

V tomto jednoduchom príklade máme dve aplikačné vlákna: hlavné vlákno a vlákno čítačky. Poďme si predstaviť scenár, v ktorom OS naplánuje tieto vlákna na dve rôzne jadrá CPU, kde:

  • Hlavné vlákno má svoju kópiu pripravený a číslo premenné v jeho jadre cache
  • Vlákno čitateľa končí tiež s jeho kópiami
  • Hlavné vlákno aktualizuje hodnoty uložené v pamäti

Na väčšine moderných procesorov sa žiadosti o zápis nebudú uplatňovať hneď po ich vydaní. V skutočnosti, procesory majú tendenciu radiť tieto zápisy do špeciálnej vyrovnávacej pamäte pre zápis. Po chvíli tieto zápisy použijú naraz na hlavnú pamäť.

So všetkým, čo sa hovorilo, keď hlavné vlákno aktualizuje číslo a pripravený premenných, neexistuje záruka toho, čo vlákno čítačky môže vidieť. Inými slovami, vlákno čítačky môže vidieť aktualizovanú hodnotu okamžite, s určitým oneskorením alebo vôbec!

Táto viditeľnosť pamäte môže spôsobiť problémy so živosťou v programoch, ktoré sa spoliehajú na viditeľnosť.

3.2. Zmena poradia

Aby toho nebolo málo, vlákno čítačky môže vidieť tieto zápisy v inom poradí, ako je skutočné poradie programu. Napríklad od prvej aktualizácie číslo premenná:

public static void main (String [] args) {nový Reader (). start (); číslo = 42; pripravený = pravda; }

Môžeme očakávať výtlačky vlákna čítačky 42. V skutočnosti je však možné vidieť nulu ako tlačenú hodnotu!

Zmena poradia je optimalizačná technika na zlepšenie výkonu. Je zaujímavé, že túto optimalizáciu môžu uplatniť rôzne komponenty:

  • Procesor môže vyprázdniť svoju vyrovnávaciu pamäť na zápis v akomkoľvek inom poradí, ako je programové poradie
  • Procesor môže použiť techniku ​​vykonávania mimo objednávky
  • Kompilátor JIT sa môže optimalizovať pomocou zmeny poradia

3.3. prchavý Poradie pamäte

Aby sme zabezpečili predvídateľné rozšírenie aktualizácií premenných na ďalšie vlákna, mali by sme použiť prchavý modifikátor týchto premenných:

public class TaskRunner {private volatile static static number; pripravený na súkromné ​​volatile static boolean; // rovnaké ako predtým }

Týmto spôsobom komunikujeme s runtime a procesorom, aby sme nezmenili poradie žiadnych pokynov týkajúcich sa prchavý premenná. Procesory tiež chápu, že by mali okamžite aktualizovať všetky aktualizácie týchto premenných.

4. prchavý a Synchronizácia vlákien

Pre viacvláknové aplikácie musíme zabezpečiť niekoľko pravidiel pre konzistentné správanie:

  • Vzájomné vylúčenie - kritickú sekciu vykoná naraz iba jedno vlákno
  • Viditeľnosť - zmeny vykonané jedným vláknom v zdieľaných údajoch sú viditeľné pre ostatné vlákna, aby sa zachovala konzistencia údajov

synchronizované metódy a bloky poskytujú obidve vyššie uvedené vlastnosti za cenu výkonu aplikácie.

prchavý je celkom užitočné kľúčové slovo, pretože môže pomôcť zabezpečiť viditeľnosť zmeny údajov bez toho, aby samozrejme došlo k ich vzájomnému vylúčeniu. Je to teda užitočné na miestach, kde nám vyhovuje viac vlákien vykonávajúcich paralelne blok kódu, ale musíme zabezpečiť vlastnosť viditeľnosti.

5. Stáva sa pred objednaním

Účinky viditeľnosti pamäte prchavý premenné presahujú rámec prchavý samotné premenné.

Aby sme to upresnili, predpokladajme, že vlákno A píše do a prchavý premenná a potom vlákno B číta to isté prchavý premenná. V takých prípadoch, hodnoty, ktoré boli viditeľné pre A pred napísaním prchavý premenná bude viditeľná pre B po prečítaní súboru prchavý premenná:

Technicky vzaté, akýkoľvek zápis do a prchavý pole sa stane pred každým nasledujúcim načítaním toho istého poľa. To je prchavý variabilné pravidlo Java Memory Model (JMM).

5.1. Prispôsobenie

Kvôli sile usporiadania pamäte pred dejom sa niekedy môžeme spojiť s vlastnosťami viditeľnosti iného prchavý premenná. Napríklad v našom konkrétnom príklade stačí označiť znak pripravený premenná ako prchavý:

public class TaskRunner {private static int number; // nie je volatilný súkromný volatilný statický boolean pripravený; // rovnaké ako predtým }

Čokoľvek pred napísaním pravda do pripravený premenná je viditeľná pre čokoľvek po prečítaní súboru pripravený premenná. Preto číslo variabilné spätné väzby na viditeľnosť pamäte vynútené pripravený premenná. Zjednodušene povedané, aj keď to nie je prchavý premenná, vykazuje a prchavý správanie.

Použitím tejto sémantiky môžeme definovať iba niekoľko premenných v našej triede ako prchavý a optimalizovať záruku viditeľnosti.

6. Záver

V tomto tutoriáli sme preskúmali viac informácií o prchavý kľúčové slovo a jeho schopnosti, ako aj vylepšenia, ktoré sa v ňom začali, počnúc Java 5.

Ako vždy, príklady kódov nájdete na GitHub.