Teoria și practica de corectare a modelului de memorie java Java, partea 1

Ce este un model de memorie de Java (JMM), și ce probleme au apărut cu ea inițial?

Grupul de experți JSR 133, care rulează aproape trei ani, a emis recent recomandări cu privire la ce să facă cu memorie modelul de memorie Modelul Java (JMM). În JMM original, au fost găsite unele deficiențe grave, ceea ce duce la complexitatea incredibil de semantica pentru concepte care trebuiau să fie simple, cum ar fi volatile. final și sincronizat. În această ediție a Java teorie și practică Brayan Getts arată modul în care va fi consolidată semantica volatile și finale, pentru a face corecții în modelul de memorie JMM. Unele dintre aceste modificări au fost deja integrate în pachetul de 1.4 JDK; alții așteaptă rândul lor să fie incluse în JDK 1.5.

Brayan Getts. Consultant Principal, Quiotix

Platforma Java a integrat împărțirea în cursuri de apă și multiprocesare în limba într-o măsură mult mai mare decât limbajele de programare anterioare. Susțineți această platformă limbaj paralelism independent și a fost multithreading soluții ambițioase și inovatoare, și poate de aceea nu este surprinzător faptul că problema a fost un pic mai complicat decât arhitecți Java crezut inițial. În centrul problemelor cu sincronizare și fir de siguranță sunt în mod intuitiv subtilități de nepătruns a memoriei Modelul Java (JMM), definite inițial în capitolul 17 al limbii Specification Java (Java Language Specification), și de grup la egal la egal re-definite de JSR 133.

De exemplu, nu toate sistemele de prelucrare a datelor multiprocesor suport cache coerentei; în cazul în care un procesor are o valoare actualizată a variabilei în cache, dar nu a fost încă resetat la memoria principală, alte procesoare nu pot vedea noua valoare. În absența a două cache diferite procesor coerentei vizibile două valori diferite în aceeași celulă de memorie. Acest lucru poate suna infricosator, dar a fost special conceput - este o modalitate de a obține o mai mare productivitate și flexibilitate - dar este, de asemenea, o sursă de complexități suplimentare pentru dezvoltatori și compilatoare care generează cod care utilizează aceste funcții.

Ce este un model de memorie, și de ce am nevoie de ea?

Modelul de memorie descrie relația dintre variabilele în program (câmpurile din instanțe câmpuri statice și elementele de matrice), precum și detaliile de nivel scăzut de stocare de memorie și de recuperare în sistemul de calcul reală. Obiectele cele din urmă stocate în memorie, dar compilator, de execuție, procesor, sau cache-ul se poate ocupa mai degrabă instalarea fără menajamente calendarul pentru valorile stabilite pentru variabile sau memoria lor de roaming. De exemplu, compilatorul poate alege pentru a optimiza variabila contor buclă, menținându-l într-un registru sau cache-ul poate întârzia o nouă valoare de resetare la o variabilă în memoria principală într-un moment potrivit. Toate aceste optimizări care vizează creșterea productivității și, în general, transparent pentru utilizator, dar în sistemele multiprocesor, pot apărea uneori aceste dificultăți.

Nu pierdeți alte articole din această serie

Modelul de memorie JMM permite compilatorului și cache-ul, mai degrabă se referă vag la ordinea în care datele sunt mutate între procesor cache (sau înregistrare) și memoria principală, cu excepția cazului în programator solicită în mod explicit vizibilitatea anumite garanții folosind sincronizate sau volatile. Acest lucru înseamnă că, în absența operațiunilor de memorie de sincronizare poate să apară într-o ordine diferită din punct de vedere al diferitelor fluxuri.

Spre deosebire de Java, în C și C ++ limbaje cum ar fi modele de memorie explicite nici un. programe C în loc să moștenesc modelul de memorie al procesorului care execută programul (deși compilatorul pentru o anumită arhitectură ar putea să știu de fapt ceva despre procesor modelul de bază, memorie, și o anumită responsabilitate pentru respectarea revine compilator). Acest lucru înseamnă că programele paralele în C poate fi realizată corect, cu o arhitectura de procesor, dar nu va rula pe alte arhitecturi de procesoare. Deși modelul de memorie JMM poate fi inițial confuz, din ea poate fi un beneficiu semnificativ: un program care este sincronizat în mod corespunzător în conformitate cu JMM, ar trebui să funcționeze corect pe orice platformă care acceptă Java.

Dezavantaje ale modelului original, Java de memorie

În timp ce JMM ca un model de memorie, definită în capitolul 17 din caietul de sarcini Java Language Specification. a fost o încercare ambițioasă de a defini un model de memorie uniform, cross-platform, are câteva neajunsuri subtile, dar semnificative. Semantica sincronizate și volatilă a fost destul de confuz, atât de mult, astfel încât mulți dezvoltatori bine pregătiți, uneori, preferă să ignore regulile, deoarece scrierea de cod corect sincronizat în conformitate cu modelul de memorie vechi a fost dificil.

Vechiul model de JMM de memorie a permis unele lucruri surprinzătoare și confuze, cum ar fi apariția câmpurilor finale nu sunt valorile care au fost stabilite în constructor (care ar putea transforma într-o mutabil obiecte imuabile), precum și rezultate neașteptate atunci când o reordonare a operațiunilor de memorie. Acesta a împiedicat, de asemenea, unele forme de program de optimizare la momentul compilarii, care, în unele cazuri, sunt eficiente. Dacă ați citit articole despre orice problema reverificat de blocare (a se vedea resurse.) Poate că vă amintiți cum confuz poate fi operație pentru reordonare, și cât de subtilă, dar probleme serioase pot strecura în codul atunci când nu sunt în mod corespunzător sincronizarea (sau în mod activ încercarea de a evita sincronizarea). Mai rău, multe programe sincronizate în mod corespunzător funcționează corect în anumite situații, cum ar fi atunci când o sarcină mică pe sisteme cu un singur procesor, sau procesoare cu modele de memorie mai puternice decât este necesar JMM.

Reordonarea termen folosit pentru a descrie mai multe clase de redistribuire reală și aparentă a operațiunilor de memorie:

  • Compilatorul poate optimiza în mod liber și pentru a reordona instrucțiuni specifice, în cazul în care nu se schimba semantica programului.
  • Procesorul este permis să efectueze operația de comandă în anumite circumstanțe.
  • Cache sunt, de obicei permisiunea de a efectua scrie înapoi la principalele variabile de memorie nu sunt în ordinea în care au fost scrise de program.

Oricare dintre aceste condiții poate duce la faptul că, în ceea ce privește funcționarea unui alt fir nu poate avea loc în ordinea specificată de program și indiferent de sursa de reordonare modelul de memorie considerate echivalente.

Obiectivele JSR 133 Grupul de experți

JSR 133 grup de experți adunat pentru a rezolva memorie Modelul Java (JMM), are următoarele obiective:

De menționat că procedurile fragmentate, cum ar fi de blocare dublu verificat, sunt zdrobite sub noul model de memorie. dublu-verificat „corecție“ de blocare nu a fost scopul eforturilor de a crea un nou model de memorie. (Cu toate acestea, noile semantica volatilă permit una dintre alternativele cele mai frecvent propuse pentru blocarea de lucru verificat de două ori în mod corect, cu toate că utilizarea acestei tehnologii este încă de dorit.)

După trei ani de activitate active, JSR 133 de grup de experți, a devenit clar că problema este mult mai insidios decât se credea nimeni. Aceasta este soarta tuturor pionieri! semantica formala final este mult mai complicat decât se aștepta inițial, și, de fapt, cu totul diferit de cel pe care îl imagina, dar semantica informale este clară și intuitivă. Acesta va fi descrisă în partea 2 din prezentul articol.

Sincronizarea și vizibilitatea

Problema № 1: Absența obiectelor imuabile

Imaginați-vă că executați următorul cod:

string s2 va avea un offset de 4 și o lungime de 4, dar se va folosi aceeași matrice de caractere care conține „/ usr / tmp“. împreună cu s1. Înainte de a rula constructorul String. constructor obiect pentru a inițializa toate domeniile, inclusiv lungimea finală în câmp și offset, la valorile implicite. La începutul designer de coarde în aceste domenii sunt setate valoare dorită. Dar, în conformitate cu modelul de memorie vechi, în lipsa de sincronizare este posibil ca unele thread va fi temporar compensate vezi câmpul cu valoarea implicită de la 0, și abia mai târziu a se vedea valoarea corectă a 4. Ca rezultat, valoarea s2 se modifică de la „/ usr“ la „/ tmp“. Acest lucru nu este ceea ce a fost menit, și, probabil, nu va funcționa pe toate platformele, sau JVM, dar caietul de sarcini al memoriei modelului vechi este permis.

Problema № 2: Reordonarea memorie volatilă și nu-volatile

Un alt domeniu important în care actualul model de memorie MJM a condus la unele rezultate foarte confuze au fost reordonare operațiunile de memorie în câmpurile volatile. Modelul existent al JMM memoriei spune că citirea și scrierea volatile trebuie să fie trimise direct la memoria principală și interzice valorile de cache în registre și ocolind procesoare cache. Acest lucru permite mai multe fire vezi întotdeauna valoarea cea mai recentă pentru variabila. Cu toate acestea, sa dovedit că această definiție volatile nu a fost la fel de util ca în primul rând de așteptat, ceea ce a dus la o confuzie semnificativă cu destinația reală volatilă.

Pentru a asigura o performanță bună în absența sincronizării, compilatorul și cache-ul de execuție este de obicei permis să reordona operația de memorie obișnuită, atâta timp cât firul de executare în prezent, nu poate recunoaște diferența. (Aceasta se numește în interiorul firului ca și în cazul în care-serial-semantica - o pe semantica kvaziposledovatelnaya). Citirea și scrierea volatile, cu toate acestea, complet distribuite într-o anumită ordine pe flux; compilator și memoria cache nu poate redistribui între ele să citească și să scrie. Din păcate, modelul de memorie JMM nu este permis să redistribuie citirea și înregistrarea de citire și scriere relativ volatile variabile obișnuite. Acest lucru înseamnă că nu putem folosi tag-uri (steaguri) volatile ca indicatori ai ce fel de operații sunt finalizate. Luați în considerare următorul cod, în care ideea este că câmpul volatil inițializat (inițializat) trebuie să indice faptul că inițializarea a fost finalizată:

Listarea 1. Folosind un câmp volatil ca o variabilă „blocare“.

Ideea aici este că variabila volatilă-inițializate acționează ca un sistem de blocare pentru a indica faptul că un set de alte operațiuni a fost finalizată. Este o idee bună, dar sub vechiul model de memorie Java, aceasta nu a funcționat, deoarece acest model permite operația de scriere nu-volatilă (de exemplu, intrarea în configOptions. Pe lângă intrarea în foaia de câmp. ConfigOptions referite) să fie redistribuite cu operațiunile de scriere volatile. Prin urmare, un alt fir poate vedea inițializată ca fiind adevărate, dar încă nu fie uniformă sau vizualizarea curentă a configOptions de câmp sau obiectele la care se referă. Vechile Semantica volatile au promis acces doar pentru a citi sau a scrie într-o variabilă, și nu au existat promisiuni în legătură cu alte variabile. Deși această abordare este mai ușor să pună în aplicare în mod eficient, sa dovedit a fi mai puțin benefică decât ideea originală.

Așa cum este definit în capitolul 17 din caietul de sarcini Java Language Specification. Modelul de memorie JMM are unele defecte grave, care permit unele lucruri în mod intuitiv de neînțeles și nedorite se întâmplă cu programele care pare destul de rezonabil. Dacă este prea dificil de a scrie clase paralele în mod corespunzător, mai multe clase paralele sunt garantate să nu funcționeze așa cum era de așteptat, și este lipsa unei platforme. Din fericire, a fost posibil să se creeze un model de memorie care ar fi în concordanță cu intuiția majoritatea dezvoltatorilor și, în același timp, nu ar încălca nici un cod care a fost corect sincronizat în conformitate cu modelul de memorie vechi. munca JSR 133 grup de experți a urmărit tocmai acest lucru. Luna viitoare ne vom uita la detaliile noului model de memorie (dintre care majoritatea au fost deja construite în JDK 1.4).