Memória hardver architektúra

A modern memóriahardver-architektúra eltér a Java belső memóriamodelljétől. Ezért meg kell értened a hardver architektúrát, hogy megtudd, hogyan működik vele a Java modell. Ez a rész az általános memóriahardver-architektúrát írja le, a következő rész pedig azt, hogy a Java hogyan működik vele.

Íme egy egyszerűsített diagram egy modern számítógép hardverarchitektúrájáról:

Memória hardver architektúra

A modern világban egy számítógép 2 vagy több processzorral rendelkezik, és ez már megszokott. Ezen processzorok némelyike ​​több maggal is rendelkezhet. Az ilyen számítógépeken lehetőség van több szál egyidejű futtatására is. Minden processzormag egy adott időpontban egy szál végrehajtására képes. Ez azt jelenti, hogy minden Java-alkalmazás eleve többszálú, és a programon belül processzormagonként egy szál futhat egyszerre.

A processzormag egy sor regisztert tartalmaz, amelyek a memóriájában (a magon belül) találhatók. Sokkal gyorsabban hajt végre műveleteket a regiszteradatokkal, mint a számítógép fő memóriájában (RAM) található adatokkal. Ennek az az oka, hogy a processzor sokkal gyorsabban tud hozzáférni ezekhez a regiszterekhez.

Minden CPU-nak saját gyorsítótár rétege is lehet. A legtöbb modern processzor rendelkezik ezzel. A processzor sokkal gyorsabban tud hozzáférni a gyorsítótárához, mint a főmemória, de nem olyan gyorsan, mint a belső regiszterei. A gyorsítótár hozzáférési sebességének értéke megközelítőleg a fő memória és a belső regiszterek hozzáférési sebessége között van.

Sőt, a processzoroknak van helye többszintű gyorsítótárnak. De ezt nem annyira fontos tudni ahhoz, hogy megértsük, hogyan működik együtt a Java memóriamodell a hardveres memóriával. Fontos tudni, hogy a processzorok rendelkezhetnek bizonyos szintű gyorsítótárral.

Bármely számítógép ugyanúgy tartalmaz RAM-ot (fő memóriaterületet). Minden mag hozzáfér a fő memóriához. A fő memóriaterület általában sokkal nagyobb, mint a processzormagok cache memóriája.

Abban a pillanatban, amikor a processzornak hozzá kell férnie a fő memóriához, beolvassa annak egy részét a gyorsítótárba. A gyorsítótárból bizonyos adatokat be tud olvasni a belső regisztereibe, majd műveleteket hajt végre azokon. Amikor a CPU-nak vissza kell írnia az eredményt a fő memóriába, az adatokat a belső regiszterből a gyorsítótárba, majd valamikor a fő memóriába üríti.

A gyorsítótárban tárolt adatok általában visszakerülnek a fő memóriába, ha a processzornak valami mást kell tárolnia a gyorsítótárban. A gyorsítótár képes egyszerre törölni a memóriáját és adatokat írni. A processzornak nem kell minden alkalommal olvasnia vagy írnia a teljes gyorsítótárat a frissítés során. Általában a gyorsítótár kis memóriablokkokban frissül, ezeket "gyorsítótárvonalnak" nevezik. Egy vagy több "gyorsítótár-sor" beolvasható a gyorsítótárba, és egy vagy több gyorsítótár visszakerülhet a fő memóriába.

Java memóriamodell és memória hardver architektúra kombinálása

Mint már említettük, a Java memóriamodell és a memória hardver architektúrája különbözik. A hardver architektúra nem tesz különbséget a szálveremek és a kupacok között. Hardveren a szál verem és a HEAP (kupac) a fő memóriában található.

A veremek és a szálkupacok részei néha jelen lehetnek a gyorsítótárban és a CPU belső regisztereiben. Ez látható a diagramon:

szál verem és HEAP

Ha objektumok és változók tárolhatók a számítógép memóriájának különböző területein, bizonyos problémák merülhetnek fel. Íme a két fő:

  • A szál által a megosztott változókon végzett módosítások láthatósága.
  • Versenyfeltétel megosztott változók olvasása, ellenőrzése és írása során.

Mindkét problémát az alábbiakban ismertetjük.

Megosztott objektumok láthatósága

Ha két vagy több szál osztozik egy objektumon az illékony deklaráció vagy szinkronizálás megfelelő használata nélkül, akkor előfordulhat, hogy a megosztott objektumon az egyik szál által végrehajtott módosítások nem lesznek láthatók a többi szál számára.

Képzelje el, hogy egy megosztott objektum kezdetben a fő memóriában van tárolva. Egy CPU-n futó szál beolvassa a megosztott objektumot ugyanazon CPU gyorsítótárába. Ott változtatásokat hajt végre az objektumon. Amíg a CPU gyorsítótárát ki nem ürítették a fő memóriába, a megosztott objektum módosított változata nem látható a más CPU-kon futó szálak számára. Így minden szál saját másolatot kaphat a megosztott objektumról, minden másolat külön CPU gyorsítótárban lesz.

A következő ábra ezt a helyzetet szemlélteti. A bal CPU-n futó egyik szál bemásolja a megosztott objektumot a gyorsítótárába, és a count értékét 2-re változtatja. Ez a változás láthatatlan a jobb CPU-n futó többi szál számára, mert a számláló frissítése még nem került vissza a fő memóriába.

A probléma megoldásához használhatja a volatile kulcsszót egy változó deklarálásakor. Biztosítani tudja, hogy egy adott változót közvetlenül a fő memóriából olvassák be, és frissítéskor mindig visszaírják a fő memóriába.

Verseny állapot

Ha két vagy több szál osztozik ugyanazon az objektumon, és egynél több szál frissíti a változókat a megosztott objektumban, akkor versenyfeltétel léphet fel.

Képzelje el, hogy az A szál beolvassa a megosztott objektum számlálóváltozóját a processzor gyorsítótárába. Képzelje el azt is, hogy a B szál ugyanezt teszi, csak egy másik processzor gyorsítótárában. Most az A szál hozzáad 1-et a szám értékéhez, és a B szál ugyanezt teszi. Most a változó kétszer nőtt - külön +1-gyel az egyes processzorok gyorsítótárában.

Ha ezeket a növeléseket egymás után hajtanák végre, a számláló változó megduplázódik, és visszaíródik a fő memóriába (eredeti érték + 2).

Ugyanakkor két lépést hajtottak végre megfelelő szinkronizálás nélkül. Függetlenül attól, hogy melyik szál (A vagy B) írja a számlálás frissített verzióját a fő memóriába, az új érték a két növekmény ellenére csak 1-gyel lesz nagyobb az eredeti értéknél.

Ez a diagram a versenyfeltételek fent leírt problémájának előfordulását szemlélteti:

A probléma megoldásához használhatja a Java szinkronizált blokkot. A szinkronizált blokk biztosítja, hogy egy adott időpontban csak egy szál léphessen be a kód adott kritikus szakaszába.

A szinkronizált blokkok azt is garantálják, hogy a szinkronizált blokkon belül elért összes változót a rendszer a fő memóriából olvassa be, és amikor a szál kilép a szinkronizált blokkból, az összes frissített változó visszakerül a fő memóriába, függetlenül attól, hogy a változó volatilisnak vagy nemnek van deklarálva.