Vodič za hlapljivu ključnu riječ na Javi

1. Pregled

U nedostatku potrebnih sinkronizacija, kompajler, vrijeme izvođenja ili procesori mogu primijeniti sve vrste optimizacija. Iako su ove optimizacije korisne većinu vremena, ponekad mogu uzrokovati suptilne probleme.

Keširanje i preuređivanje su među onim optimizacijama koje nas mogu iznenaditi u istodobnim kontekstima. Java i JVM pružaju mnogo načina za upravljanje redoslijedom memorije i hlapljiv ključna riječ je jedan od njih.

U ovom ćemo se članku usredotočiti na ovaj temeljni, ali često pogrešno shvaćen koncept u jeziku Java - hlapljiv ključna riječ. Prvo ćemo započeti s malo pozadine o tome kako funkcionira osnovna računalna arhitektura, a zatim ćemo se upoznati s redoslijedom memorije u Javi.

2. Zajednička višeprocesorska arhitektura

Procesori su odgovorni za izvršavanje programskih uputa. Stoga iz RAM-a trebaju dohvatiti i programske upute i potrebne podatke.

Kako su CPU sposobni izvršavati značajan broj uputa u sekundi, dohvaćanje iz RAM-a nije toliko idealno za njih. Da bi poboljšali ovu situaciju, procesori se koriste trikovima kao što su Izvršenje naloga, Predviđanje grana, Spekulativno izvršenje i, naravno, Keširanje.

Ovdje nastupa sljedeća hijerarhija memorije:

Kako različite jezgre izvršavaju više uputa i manipuliraju s više podataka, ispunjavaju svoje predmemorije relevantnijim podacima i uputama. To će poboljšati ukupnu izvedbu na štetu uvođenja izazova koherencije predmemorije.

Pojednostavljeno, trebali bismo dvaput razmisliti što se događa kada jedna nit ažurira predmemoriranu vrijednost.

3. Kada koristiti hlapljiv

Da bismo se više proširili na usklađenost predmemorije, posudimo jedan primjer iz knjige Java Concurrency in Practice:

javna klasa TaskRunner {privatni statički int broj; spreman za privatnu statičku logičku vrijednost; privatni statički čitač klasa proširuje Thread {@Override public void run () {while (! ready) {Thread.yield (); } System.out.println (broj); }} public static void main (String [] args) {new Reader (). start (); broj = 42; spreman = istina; }}

The TaskRunner razred održava dvije jednostavne varijable. U svojoj glavnoj metodi stvara još jednu nit koja se vrti na spreman promjenjiva sve dok je lažno. Kad varijabla postane pravi, nit će jednostavno ispisati broj varijabilna.

Mnogi mogu očekivati ​​da će ovaj program nakon kratkog kašnjenja jednostavno ispisati 42. Međutim, u stvarnosti kašnjenje može biti puno duže. Možda čak i zauvijek visi, ili čak ispiše nulu!

Uzrok ovih anomalija je nedostatak odgovarajuće vidljivosti i preuređivanja memorije. Procijenimo ih detaljnije.

3.1. Vidljivost memorije

U ovom jednostavnom primjeru imamo dvije niti aplikacije: glavnu nit i nit čitača. Zamislimo scenarij u kojem OS planira te niti na dvije različite CPU jezgre, gdje:

  • Glavna nit ima svoju kopiju spreman i broj varijable u svojoj predmemoriji jezgre
  • Nit čitača završava i sa svojim primjercima
  • Glavna nit ažurira predmemorirane vrijednosti

Na većini modernih procesora zahtjevi za pisanje neće se primijeniti odmah nakon izdavanja. Zapravo, procesori imaju tendenciju stavljati one zapise u red čekanja u posebnom međuspremniku za pisanje. Nakon nekog vremena primijenit će ta pisanja u glavnu memoriju odjednom.

Uz sve rečeno, kada glavna nit ažurira broj i spreman varijable, ne postoji jamstvo o onome što nit čitača može vidjeti. Drugim riječima, nit čitača može odmah vidjeti ažuriranu vrijednost ili s određenim kašnjenjem ili nikako!

Ova vidljivost memorije može uzrokovati probleme sa živošću u programima koji se oslanjaju na vidljivost.

3.2. Preuređivanje

Da stvar bude još gora, nit čitača može vidjeti ona pisanja u bilo kojem redoslijedu osim u stvarnom redoslijedu programa. Na primjer, otkad smo prvi put ažurirali broj varijabla:

javna statička void glavna (String [] args) {new Reader (). start (); broj = 42; spreman = istina; }

Možemo očekivati ​​ispise niti čitača 42. Međutim, zapravo je moguće vidjeti nulu kao ispisanu vrijednost!

Preuređivanje je tehnika optimizacije za poboljšanje izvedbe. Zanimljivo je da ovu optimizaciju mogu primijeniti različite komponente:

  • Procesor može isprati svoj međuspremnik za pisanje u bilo kojem redoslijedu koji nije redoslijed programa
  • Procesor može primijeniti tehniku ​​izvršavanja bez narudžbe
  • JIT kompajler može optimizirati preuređivanjem

3.3. hlapljiv Red memorije

Da bismo osigurali da se ažuriranja varijabli predvidljivo šire na druge niti, trebali bismo primijeniti hlapljiv modifikator tih varijabli:

javna klasa TaskRunner {privatni volatilni statički int broj; spreman za privatnu hlapljivu statičku logičku vrijednost; // isto kao prije }

Na taj način komuniciramo s runtimeom i procesorom kako ne bismo preuredili bilo koju naredbu koja uključuje hlapljiv varijabilna. Također, procesori razumiju da bi trebali odmah isprati sva ažuriranja ovih varijabli.

4. hlapljiv i sinkronizacija niti

Za višenitne aplikacije moramo osigurati nekoliko pravila za dosljedno ponašanje:

  • Međusobno izuzeće - samo jedna nit istodobno izvršava kritični odjeljak
  • Vidljivost - promjene koje je jedna nit napravila u zajedničkim podacima vidljive su ostalim nitima radi održavanja dosljednosti podataka

sinkronizirano metode i blokovi pružaju oba gore navedena svojstva, po cijenu izvedbe aplikacije.

hlapljiv je vrlo korisna ključna riječ jer je može vam osigurati vidljivost promjene podataka bez, naravno, uzajamnog izuzimanja. Stoga je korisno na mjestima na kojima se slažemo s više niti koje paralelno izvršavaju blok koda, ali moramo osigurati svojstvo vidljivosti.

5. Događa se prije naručivanja

Učinci vidljivosti memorije na hlapljiv varijable se šire izvan okvira hlapljiv same varijable.

Da stvar bude konkretnija, pretpostavimo da nit A piše u a hlapljiv varijabla, a zatim nit B čita isto hlapljiv varijabilna. U takvim slučajevima, vrijednosti koje su bile vidljive A prije pisanja hlapljiv varijabla bit će vidljiva B nakon čitanja znaka hlapljiv varijabla:

Tehnički gledano, svako pisanje na a hlapljiv field događa se prije svakog sljedećeg čitanja istog polja. Ovo je hlapljiv varijabilno pravilo Java memorijskog modela (JMM).

5.1. Piggybacking

Zbog snage događaja prije naručivanja memorije, ponekad možemo podmetnuti svojstva vidljivosti drugog hlapljiv varijabilna. Na primjer, u našem konkretnom primjeru samo trebamo označiti znak spreman varijabla kao hlapljiv:

javna klasa TaskRunner {privatni statički int broj; // nije volatile private volatile static boolean ready; // isto kao prije }

Bilo što prije pisanja pravi prema spreman varijabla je vidljiva bilo čemu nakon čitanja spreman varijabilna. Stoga je broj varijabilni piggyback na vidljivosti memorije koju provodi spreman varijabilna. Jednostavno rečeno, iako nije a hlapljiv varijabla, ona pokazuje a hlapljiv ponašanje.

Korištenjem ove semantike možemo definirati samo nekoliko varijabli u našoj klasi kao hlapljiv i optimizirati jamstvo vidljivosti.

6. Zaključak

U ovom uputstvu istražili smo više o hlapljiv ključna riječ i njezine mogućnosti, kao i poboljšanja u njemu počevši od Jave 5.

Kao i uvijek, primjeri koda mogu se naći na GitHubu.