Haladó Olvasási idő: ~17 perc

Memory Model

JMM, happens-before reláció, láthatóság és atomicitás

Java Memory Model

A JMM (JSR-133) meghatározza a szabályokat, amelyek szerint a szálak a közös memórián keresztül kommunikálnak — pontosan megadva, hogy az egyik szál által végzett írás mikor válik láthatóvá egy másik szál olvasásai számára.

1. Definíció

Mi ez?

A Java Memory Model (JMM), amelyet a JSR-133 specifikáció szabványosított és a Java 5-ben vezettek be, az a formális leírás, amely meghatározza, hogyan lépnek kölcsönhatásba a szálak a megosztott memórián keresztül. Egy alapvető kérdést válaszol meg:

"Mikor garantált, hogy az A szál által írt értéket a B szál látni fogja?"

Jól definiált memory model nélkül a JVM, a fordító (JIT) és a CPU szabadon átrendezheti az utasításokat és gyorsítótárazhatja az értékeket — a single-threaded teljesítmény javítása érdekében, de a multi-threaded helyesség kárára.

Miért létezik?

A modern hardver és a fordítók számos, forráskód szinten láthatatlan optimalizációt alkalmaznak:

  • CPU cache-ek: minden magnak saját L1/L2 gyorsítótára van; az írások nem feltétlenül kerülnek azonnal a főmemóriába.
  • Store buffer-ek: a CPU által kibocsátott írás a store buffer-ben maradhat, mielőtt globálisan láthatóvá válna.
  • Utasítás-átrendezés: a fordítók (JIT) és az out-of-order CPU-k eltérő sorrendben hajthatják végre az utasításokat, amennyiben az eredmény single-threaded szempontból helyes.

Ezek az optimalizációk biztonságosak egyszálas környezetben. Többszálasban azonban ahhoz vezethetnek, hogy az egyik thread elavult vagy részleges képet lát a másik thread munkájáról — data race és visibility bug következhet be.

Hova illeszkedik?

A JMM a Java nyelv/könyvtárak és az alapul szolgáló hardver közé helyezkedik el. Ez az a szerződés, amelyre:

  • A könyvtárírók (pl. java.util.concurrent) támaszkodnak biztonságos absztrakciók felépítésekor.
  • Az alkalmazásfejlesztők implicit módon hivatkoznak, valahányszor volatile-t, synchronized-ot vagy java.util.concurrent primitíveket használnak.
  • A JVM implementáció szerzőjének be kell tartania, amikor natív kódra fordít bármely hardverplatformon.

2. Alapfogalmak

2.1 A probléma JMM nélkül

Képzeljünk el két thread-et, amelyek az x és flag változókat osztják meg:

Példa idővonal szinkronizáció nélkül:

  1. Thread 1 beírja, hogy x = 1.
  2. Thread 1 beírja, hogy flag = true.
  3. Thread 2 azt látja, hogy flag == true.
  4. Thread 2 ennek ellenére még mindig láthatja, hogy x == 0, mert a láthatóság nem garantált.

Szinkronizáció nélkül:

  • A fordító az Thread 1-ben átrendezheti a két írást (flag előbb, mint x).
  • Thread 2 olvashat a saját CPU cache-éből, és láthatja flag = true-t, miközben még x = 0.
  • A CPU store buffer-e eltérő sorrendben ürítheti ki az írásokat.

Mindez megtörténhet valódi hardveren (különösen ARM és POWER architektúrán).

2.2 Happens-Before (HB) reláció

A happens-before reláció a JMM magja. Ha A happens-before B, akkor:

  • Az A összes mellékhatása (és minden, ami A előtt HB) látható B számára.
  • A JVM/CPU nem rendezheti át ezeket oly módon, hogy sértse ezt a garanciát.

A HB nem valós idejű sorrend. A lehet happens-before B, még akkor is, ha B fizikailag nanoszekundumokkal korábban futott — ez logikai sorrendezési garancia.

Beépített HB szabályok

Szabály Leírás
Program order Egy thread-en belül minden akció happens-before az utána következő akció.
Monitor unlock Egy monitor feloldása (unlock) happens-before ugyanazon monitor bármely következő lezárása (lock).
Volatile write Egy volatile mezőre való írás happens-before ugyanazon mező bármely következő olvasása.
Thread start A thread.start() hívás happens-before az indított thread-ben végrehajtott bármely akció.
Thread termination Egy thread összes akciója happens-before bármely thread, amely join()-nal vagy isAlive()-val észleli a leállást.
Interruption Az interrupt() hívás happens-before az interrupted thread észleli az megszakítást.
Finalizer Egy konstruktor befejezése happens-before az adott objektum finalizer-ének kezdete.
Tranzitivitás Ha A HB B és B HB C, akkor A HB C.

HB vizualizálva

HB a monitor szabály alapján:

  1. Thread 1 beírja, hogy x = 1.
  2. Thread 1 feloldja a monitort.
  3. Thread 2 lezárja ugyanazt a monitort.
  4. Thread 2 olvasáskor már köteles látni az x = 1 értéket.

2.3 Memóriaarchitektúra és a visibility probléma

Szinkronizáció nélkül a láthatóság tipikusan két szinten dől el:

  • Thread-local cache vagy regiszterek — a thread itt tarthat meg elavult értékeket.
  • Main memory — a frissített érték már itt lehet, de a többi thread nem köteles azonnal látni.

Ezért fordulhat elő, hogy Thread 2 egy régi értéket olvas tovább, miközben Thread 1 már kiírta az újat.

Ha nincs happens-before él a Thread 1 írása és a Thread 2 olvasása között, a JMM semmilyen garanciát nem ad arra, hogy Thread 2 valaha látni fogja a frissített értéket. Ez a visibility probléma.

2.4 Atomicity

Az atomicity azt jelenti, hogy egy műveletet egyetlen, oszthatatlan egységként hajtanak végre — egyetlen más thread sem figyelhet meg részlegesen elvégzett állapotot.

Művelet Atomikus? Megjegyzés
int / boolean / byte / short / char / float olvasás/írás A JLS garantálja
long / double olvasás/írás ⚠️ 32-bites JVM-en NEM atomikus (két 32-bites művelet)
volatile long / volatile double olvasás/írás volatile kényszeríti az atomikus 64-bites hozzáférést
i++ (bármely típus) Read-modify-write: három külön művelet
AtomicInteger.incrementAndGet() CAS (compare-and-swap) hardveres utasítást használ

Kulcsgondolat: Még ha egy mező volatile is, az összetett i++ művelet nem atomikus. Beolvas, növel, majd visszaír — egy másik thread közbeszúródhat az olvasás és az írás közé.

2.5 `volatile`

Egy mező volatile-nak deklarálása két garanciát nyújt:

  1. Visibility: A volatile mezőre való írás happens-before az adott mező bármely következő olvasása.
  2. Átrendezés megakadályozása: A JVM memory barrier-eket szúr be a volatile hozzáférések köré, megakadályozva a fordítót és a CPU-t, hogy közönséges olvasásokat/írásokat rendezzen át egy volatile hozzáférésen keresztül.

Mit NEM garantál a volatile:

  • Összetett műveletek atomicitását (flag++ nem atomikus)
  • Kizárást (mutual exclusion)

Mikor érdemes volatile-t használni:

  • Egyszerű állapotjelzők, amelyeket több thread olvas (pl. volatile boolean running)
  • Immutable objektumreferencia biztonságos közzétételéhez (egy író, sok olvasó)
  • Double-checked locking (DCL) minta — a referencia mezőnek kötelezően volatile-nak kell lennie

2.6 `synchronized`

A synchronized egyszerre biztosít:

  1. Kizárást (atomicity a blokkon belül): Egyszerre csak egy thread tartja a monitor-t.
  2. Visibility (happens-before): Az unlock előtti összes írás látható minden olyan thread számára, amely ezt követően ugyanazt a lock-ot szerzi meg.
Thread 1                         Thread 2
synchronized(lock) {             synchronized(lock) {
  x = 1;          ──── HB ────►    read x  → 1 ✅
  y = 2;                           read y  → 2 ✅
}                                }

2.7 Memory Barrier-ek / Fence-ek

A volatile és a synchronized memory barrier utasításokra fordítódik le, amelyek megakadályozzák a CPU-t, hogy load-okat és store-okat rendeljen át a barrier-en keresztül.

Barrier típus Hatás
LoadLoad Egyetlen load sem rendezhető egy korábbi load elé
StoreStore Egyetlen store sem rendezhető egy korábbi store elé
LoadStore Egyetlen store sem rendezhető egy korábbi load elé
StoreLoad Egyetlen load sem rendezhető egy korábbi store elé — a legköltségesebb

Egy volatile írás StoreStore barrier-t szúr be elé, StoreLoad barrier-t utána.
Egy volatile olvasás LoadLoad + LoadStore barrier-t szúr be utána.
A synchronized hatékonyan teljes fence-t szúr be a lock megszerzésekor és elengedésekor.


3. Gyakorlati használat

Mikor érdemes `volatile`-t használni

  • Egyszerű boolean flag-ek vagy státuszjelzők (volatile boolean shutdown)
  • Egyetlen immutable objektumreferencia biztonságos közzétételéhez
  • A double-checked locking (DCL) referencia mezőjéhez
  • Számlálók, ahol csak visibility szükséges (egy thread ír, mások csak olvasnak)

Mikor érdemes `synchronized`-ot használni

  • Bármely kizárást (mutual exclusion) igénylő esetben (check-then-act, read-modify-write)
  • Amikor több mezőt kell atomikusan együtt frissíteni
  • Ha a volatile egyedül nem elegendő (összetett műveletek)

Mikor érdemes `AtomicXxx`-et használni

  • Erősen versengett single-variable számláló vagy akkumulátor
  • CAS-alapú non-blocking algoritmusok
  • Részesítsd előnyben az AtomicInteger-t, AtomicLong-ot, AtomicReference-t a volatile + manuális CAS helyett

Mikor érdemes Immutable objektumokat használni

Ha egy objektum immutable (minden mező final, a konstruktorban kerül beállításra, this nem szökik el), bármilyen mechanizmuson keresztül biztonságosan közzétehető — beleértve a sima értékadást is. A JMM speciálisan kezeli a final mezőket: értékeik garantáltan láthatók a konstruktor befejezése után.

Safe Publication

Egy objektum biztonságosan közzétett (safely published), ha a rá mutató referenciát megfelelően szinkronizált mechanizmuson keresztül teszik láthatóvá más thread-ek számára:

Mechanizmus Miért biztonságos
static inicializáló Az osztálybetöltést a JVM szinkronizálja
final mező A JMM garantálja a befagyasztást a konstruktor után
volatile mező Volatile írás HB volatile olvasás
Megfelelően zárolt mező Monitor szabály
java.util.concurrent gyűjtemények Belső volatile/lock használat

Double-Checked Locking (DCL)

A DCL egy elterjedt singleton minta. volatile nélkül Java 5 előtt hibás volt:

// ❌ HIBÁS — a referencia részlegesen inicializált állapotban látható
class Singleton {
    private static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {               // 1. ellenőrzés (lock nélkül)
            synchronized (Singleton.class) {
                if (instance == null) {       // 2. ellenőrzés (lock-kal)
                    instance = new Singleton(); // átrendezés lehetséges!
                }
            }
        }
        return instance;
    }
}

new Singleton() három művelet: allokálás, mezők inicializálása, referencia értékadása. A JIT átrendezheti az értékadást az inicializálás elé. Egy másik thread null-tól eltérő, de részlegesen inicializált objektumot láthat.

// ✅ HELYES — volatile megakadályozza az értékadás átrendezését
class Singleton {
    private static volatile Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

4. Kód példák

1. példa — Visibility bug (végtelen ciklus)

// volatile nélkül ez a ciklus sosem ér véget!
public class VisibilityBug {
    private static boolean running = true; // ❌ nem volatile

    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            while (running) { /* pörgés */ }
            System.out.println("Megállt.");
        });
        worker.start();

        Thread.sleep(100);
        running = false; // Thread 1 ír, de a worker sosem láthatja!
        System.out.println("running = false beállítva");
    }
}

Megoldás: private static volatile boolean running = true;

2. példa — Hibás vs helyes Double-Checked Locking

// ❌ Hibás DCL — hiányzó volatile
class BrokenSingleton {
    private static BrokenSingleton instance;
    public static BrokenSingleton get() {
        if (instance == null) {
            synchronized (BrokenSingleton.class) {
                if (instance == null) instance = new BrokenSingleton();
            }
        }
        return instance; // Részlegesen inicializált objektumot adhat vissza!
    }
}

// ✅ Helyes DCL — volatile az instance-on
class CorrectSingleton {
    private static volatile CorrectSingleton instance;
    public static CorrectSingleton get() {
        if (instance == null) {
            synchronized (CorrectSingleton.class) {
                if (instance == null) instance = new CorrectSingleton();
            }
        }
        return instance;
    }
}

3. példa — volatile számláló csapda (nem atomikus!)

public class VolatileCounter {
    private volatile int count = 0; // ❌ volatile NEM teszi atomikussá a ++-t!

    public void increment() {
        count++; // olvasás → növelés → írás (3 lépés, versenyhelyzet lehetséges)
    }

    public int get() { return count; }
}

// 1000 thread-del, mindegyik egyszer hívva az increment()-et, a végeredmény < 1000 lehet!

4. példa — Helyes atomikus számláló

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    private final AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet(); // ✅ CAS-alapú, lock-free, atomikus
    }

    public int get() { return count.get(); }
}

5. példa — Biztonságos közzététel final mezőkön keresztül

// Immutable objektum: bármilyen referencián keresztül biztonságosan közzétehető
public final class ImmutablePoint {
    private final int x;
    private final int y;

    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
        // A konstruktor visszatérése után minden olvasó garantáltan látja x-et és y-t
    }

    public int getX() { return x; }
    public int getY() { return y; }
}

// Még egy sima (nem volatile) értékadás is biztonságos immutable objektumokhoz
// statikus inicializálón keresztül közzétéve:
public class Config {
    public static final ImmutablePoint ORIGIN = new ImmutablePoint(0, 0); // ✅ biztonságos
}

Gyakori csapda — Flag ellenőrzése szinkronizáció nélkül

// ❌ race condition: check-then-act atomicitás nélkül
if (!map.containsKey(key)) {
    map.put(key, computeValue()); // közbeszúródhat egy másik thread az ellenőrzés és a put közé
}

// ✅ ConcurrentHashMap.computeIfAbsent használata az atomikus check-then-put-hoz
map.computeIfAbsent(key, k -> computeValue());

5. Trade-offok

Szempont volatile synchronized AtomicXxx
Teljesítmény Alacsony overhead, ~memory barrier Magasabb — lock megszerzés/elengedés OS-t vagy spinlock-ot érint Alacsony–közepes — CAS versengés esetén újra kísérelhet
🔒 Kizárás ❌ Nincs ✅ Igen ✅ Változónként (CAS)
👁️ Visibility ✅ Igen ✅ Igen ✅ Igen
🔢 Összetett műveletek ❌ Nem atomikus ✅ Ha azonos blokkon belül ✅ Metódusonként (pl. compareAndSet)
💾 Memória Minimális Monitor objektum overhead Objektum változónként
🔧 Karbantarthatóság Egyszerű flag-ekhez Tiszta szándék, ismerős Jó számlálókhoz/referenciákhoz
🔄 Skálázhatóság Magas — nincs blokkolás Versengés csökkenti az átvitelt Magas — non-blocking algoritmusok

A false sharing egy rejtett teljesítményproblémát jelent: ha két volatile mező ugyanabban a CPU cache line-ban van, minden írás érvényteleníti az egész cache line-t az összes CPU számára, még akkor is, ha különböző thread-ek különböző változókhoz férnek hozzá. A hot mezőket elválasztva padding vagy @jdk.internal.vm.annotation.Contended segítségével lehet megoldani.


6. Gyakori hibák

❌ 1. hiba: Azt feltételezni, hogy `volatile` atomikussá teszi az összetett műveleteket

// ❌ volatile NEM teszi atomikussá a ++-t!
private volatile int counter = 0;
public void increment() { counter++; } // DATA RACE

// ✅ Használj AtomicInteger-t
private final AtomicInteger counter = new AtomicInteger(0);
public void increment() { counter.incrementAndGet(); }

❌ 2. hiba: Double-checked locking `volatile` nélkül

// ❌ Java 5 előtt hibás, és még ma is helytelen — a JIT átrendezheti
private static Resource instance;
// ...
if (instance == null) { synchronized(...) { if (instance == null) instance = new Resource(); } }

// ✅ Kötelezően volatile-nak kell lennie
private static volatile Resource instance;

❌ 3. hiba: Mutable állapot megosztása bármilyen szinkronizáció nélkül

// ❌ Mindkét thread szinkronizáció nélkül fér hozzá a 'list'-hez — ConcurrentModificationException / adatvesztés
List<String> list = new ArrayList<>();
// Thread 1: list.add("a");
// Thread 2: list.add("b");

// ✅ Használj thread-safe gyűjteményt
List<String> list = Collections.synchronizedList(new ArrayList<>());
// vagy
List<String> list = new CopyOnWriteArrayList<>();

❌ 4. hiba: Túlzott szinkronizáció

// ❌ Immutable értéken minden olvasást zárni — szükségtelen versengés
public synchronized String getImmutableConfig() { return config; }

// ✅ Immutable / final mezőkhoz nem kell zárás
private final String config = "value";
public String getConfig() { return config; }

❌ 5. hiba: A happens-before összekeverése a valós idejű sorrenddel

// TÉVES gondolkodás: "Thread 1 hamarabb ír, mint Thread 2 olvas, tehát Thread 2 látni fogja"
// A HB LOGIKAI garancia, nem időbeli.
// HB él (volatile/synchronized/stb.) nélkül NINCS visibility garancia,
// függetlenül attól, hogy Thread 1 mikor futott valós időben.

7. Senior-szintű meglátások

JSR-133 és a Java 5 újraírás

Az eredeti Java Memory Model (JDK 1.0–1.4) közismerten hibás volt — még a double-checked locking helyes viselkedésére sem adott garanciát. A JSR-133 újraírta a JMM-et Java 5-ben, bevezetve a happens-before formalizmust, a megerősített volatile szemantikát és a final mező garanciákat, amelyek a modern Java konkurenciájának alapját képezik.

CPU memóriamodellek: x86 TSO vs ARM

A JMM hardverfüggetlen, de implementációs költsége CPU-nként eltér:

  • x86 (TSO — Total Store Order): Az x86-nak már eleve viszonylag erős memóriamodellje van. A volatile olvasások lényegében ingyenesek (csak egy load); csak a volatile írásokhoz kell LOCK XCHG vagy MFENCE. Ezért sok JMM bug csak ARM-on vagy POWER-en mutatkozik meg.
  • ARM/POWER (gyenge memóriamodell): Mindkét irányhoz (olvasás és írás) explicit dmb / sync barrier utasítások szükségesek, ami drágábbá teszi a volatile-t.

False Sharing és `@Contended`

Ha két hot volatile mező ugyanabban a CPU cache line-ban van (jellemzően 64 byte), bármelyik írása érvényteleníti az egész cache line-t az összes CPU számára — ez a false sharing, egy csendes teljesítmény-gyilkos a nagy áteresztőképességű konkurens kódban.

// ❌ False sharing: counter és flag valószínűleg ugyanabban a cache line-ban van
class Shared {
    volatile long counter = 0;
    volatile boolean flag = false;
}

// ✅ @Contended (JDK belső, --add-opens vagy JVM flag szükséges)
// vagy manuális padding
class Padded {
    volatile long counter = 0;
    long p1, p2, p3, p4, p5, p6, p7; // 56 byte padding
    volatile boolean flag = false;
}

VarHandle (Java 9+)

A java.lang.invoke.VarHandle részletes memóriarendezési szemantika feletti kontrollt biztosít a teljes volatile overhead nélkül:

Hozzáférési mód Rendezési garancia
getPlain / setPlain Nincs rendezés (nem volatile-ként)
getOpaque / setOpaque Koherens, változónkénti rendezés
getAcquire / setRelease Acquire/release szemantika (olcsóbb, mint a teljes volatile)
getVolatile / setVolatile Teljes volatile szemantika
compareAndSet CAS teljes volatile szemantikával

Az acquire/release (amelyet a java.util.concurrent is széles körben alkalmaz) olcsóbb, mint a teljes volatile, gyenge memóriamodellű architektúrákon, mivel csak egyirányú barrier-eket igényel.

`final` mezők és Safe Publication

A JMM speciális garanciát ad a final mezőkre: ha a konstruktor lefut és a referencia nem szökik el a konstruktorból, minden thread helyesen inicializált értékeket fog látni az összes final mezőn semmilyen további szinkronizáció nélkül. Ez az immutabilitás-alapú safe publication alapja.

Lock-free algoritmusok és CAS

Az AtomicInteger, AtomicReference stb. a compare-and-swap (CAS) műveletet használja — egyetlen atomikus CPU utasítást (CMPXCHG x86-on), amely egy oszthatatlan lépésben olvas, összehasonlít és feltételesen ír. Ez lehetővé tesz non-blocking algoritmusokat, amelyek mérsékelt versengés esetén nagyobb áteresztőképességgel rendelkeznek, mint lock-alapú megfelelőik. Igen erős versengés esetén a CAS retry loop-ok (ABA probléma, versengő CAS) rosszabb teljesítményre degradálódhatnak, mint egy jól hangolt lock.


8. Szószedet

Fogalom Definíció
JMM Java Memory Model — a formális specifikáció (JSR-133), amely meghatározza, hogyan osztják meg a thread-ek a memóriát.
Happens-Before Logikai sorrendezési garancia: ha A HB B, akkor A összes hatása látható B számára.
Visibility Annak lehetősége, hogy az egyik thread írása egy másik thread olvasásában megfigyelhető-e.
Atomicity Egy művelet tulajdonsága: egyetlen oszthatatlan egységként hajtódik végre, közbülső állapot nem figyelhető meg.
volatile Java kulcsszó, amely kikényszeríti a visibility-t és megakadályozza az átrendezést, de nem biztosít kizárást.
synchronized Java kulcsszó, amely monitor lock-on keresztül biztosít kizárást és visibility-t.
Memory Barrier CPU/fordító utasítás, amely megakadályozza a read/write műveletek átrendezését a barrier-en keresztül.
Race Condition Hiba, amelynél az eredmény a thread-ek relatív ütemezésétől függ.
Data Race Két thread egyidejűleg fér hozzá ugyanahhoz a memóriahelyre, legalább az egyikük ír, szinkronizáció nélkül.
Monitor A synchronized által Java-ban használt, objektumonkénti lock mechanizmus.
Safe Publication Egy objektumreferencia láthatóvá tétele más thread-ek számára oly módon, hogy az objektum állapota is garantáltan látható.
Reordering A fordító vagy CPU megváltoztatja a memóriaműveletek sorrendjét (egyszálasnál biztonságos, többszálasnál veszélyes).
Store Buffer CPU hardveres puffer, amely a függőben lévő írásokat tartja, mielőtt elérnek a cache-be/főmemóriába.
Cache Coherence Hardveres protokoll (pl. MESI), amely biztosítja, hogy minden CPU végül megegyezzen egy megosztott memóriahely értékéről.
False Sharing Két thread akaratlanul verseng ugyanazon CPU cache line-on, egymástól független mezők közelsége miatt.
CAS Compare-And-Swap — atomikus CPU utasítás, amelyet lock-free adatstruktúrák implementálásához használnak.
Acquire/Release Gyengébb memóriarendezési szemantika: acquire (olvasás után) megakadályozza az azt követő read/write-ok előre mozgását; release (írás előtt) megakadályozza a megelőző read/write-ok hátra mozgását.

9. Cheatsheet

  • 🔑 A HB a JMM alapszabálya: hozz létre happens-before élt, különben nincs visibility garancia.
  • 🏷️ volatile = visibility + átrendezés megakadályozása; NEM kizárás vagy összetett művelet atomicitása.
  • 🔒 synchronized = kizárás + visibility; akkor használd, ha több mezőt vagy összetett műveleteket kell atomikusan kezelni.
  • ⚛️ AtomicInteger / AtomicReference = lock-free, CAS-alapú atomikus műveletek egyetlen változóhoz.
  • ♾️ Az i++ SOHA nem atomikus, még volatile mezőn sem — olvasás + módosítás + írás.
  • 🏗️ A DCL mintához volatile szükséges a referencia mezőn a részlegesen inicializált objektumok elkerüléséhez.
  • 🧊 A final mezők konstruktor után szabadon biztonságosak — az immutable objektumok a legegyszerűbb szálbiztonságot nyújtják.
  • 🐌 A false sharing csendben öli a teljesítményt; védd a hot volatile mezőket padding-gel vagy @Contended-del.
  • 🔧 A VarHandle (Java 9+) acquire/release szemantikát kínál — olcsóbb, mint a teljes volatile gyenge memóriamodellű CPU-kon.
  • ⚠️ Happens-before ≠ valós idő: logikai rendezés, nem kronológiai.

🎮 Játékok

8 kérdés