---
title: 'Java Bellek Modeli'
source: 'https://academia.sh/tr/kurslar/jvm-ve-eszamanlilik/java-bellek-modeli'
course: 'JVM, Eşzamanlılık ve Başarım'
language: tr
updated: '2026-08-23T07:00:56+00:00'
license: 'CC BY-SA 4.0'
---

# Java Bellek Modeli

Sekiz yayımlama düzeneğinden dördü hem yazanın hem okuyanın harekete geçmesini istiyor, biri yükü tek başına yazana (final alan), biri tek başına okuyana (join) yüklüyor, ikisi ise görünürlük sorusunu hiç sormuyor. Senkronizasyonsuz bir alanın görünürlük kusuru koşturularak gösterilmiyor; kural olarak modelleniyor.

Bir önceki ders bir değeri hep aynı iş parçacığının hem yazıp hem okuduğu kurulumlarla
çalıştı; `basladi.await()` ile `t.join()` yalnız **sırayı** kurdu, görünürlüğü sağlayan
mekanizma hiç adı konmadan zaten oradaydı. Bu ders o mekanizmanın adını koyuyor. Bir değeri
bir iş parçacığı yazdığında ve başka bir iş parçacığı okuduğunda, okuyanın yazılan değeri
görmesi **otomatik değildir** — görülmesini sağlayan şey, iki eylem arasında dilin tanıdığı bir
sıra kurulmuş olmasıdır. Bu sıraya **önce-olur bağıntısı** deniyor ve kursun bu dersteki
sorusu şu: sekiz farklı yayımlama düzeneğinin her birinde, bu bağıntıyı kurmak için **hangi
taraf** ne yapmak zorunda — yalnız yazan mı, yalnız okuyan mı, ikisi de mi, yoksa hiçbiri mi?

Sorunun "otomatik değildir" kısmı önceki dersteki örneklerle çelişiyor gibi görünebilir; orada
hiçbir ek çaba harcanmadan her değer doğru okundu. Çelişki yüzeyseldir: önceki dersteki her
kurulum zaten `CountDownLatch` ya da `join` kullanıyordu, ve bu iki araç — aşağıda görüleceği
gibi — kendi başlarına birer önce-olur kaynağıdır. Görünürlük orada da otomatik değildi;
yalnız gerekli bağıntı zaten kuruluydu ve bu ders o kuruluşun neden yeterli olduğunu
adlandırıyor.

## Önce-Olur Bağıntısı: Bir Kural Olarak

- **EZ5** — Önce-olur bağıntısı bir zamanlama iddiası değildir; iki eylemin **hangi sırayla
  gerçekleştiğini** söylemez, yalnız biri gerçekleştikten sonra ötekinin sonuçlarının **görünür
  olacağını** garanti eder. Bu derste bağıntı hiçbir yerde ölçülmüyor, yalnız kural olarak
  kullanılıyor.

Dilin tanıdığı önce-olur kuralları arasında bu derste kullanılanlar şunlar: bir iş
parçacığındaki eylemler program sırasına göre birbirinden önce-olur; bir izleyici kilidinin
serbest bırakılması, aynı kilidi sonra alan her eylemden önce-olur; uçucu (`volatile`) bir
alana yazmak, aynı alanı sonra okuyan her eylemden önce-olur; bir iş parçacığının
başlatılması (`start()`), o iş parçacığındaki bütün eylemlerden önce-olur; bir iş
parçacığındaki bütün eylemler, başka bir iş parçacığının o iş parçacığında `join()`'den
başarıyla dönmesinden önce-olur; ve bir `final` alanın kurucudaki değeri, kurucu tamamlandıktan
sonra o nesneye **güvenli yollarla** ulaşan her iş parçacığından önce-olur. Bu liste, aşağıdaki
sekiz düzeneği kategorize etmek için yeterli.

Bu kuralların hepsi **geçişlidir**: A eylemi B'den önce-olur ve B eylemi C'den önce-olursa, A
da C'den önce-olur — ara halka farklı bir mekanizma kullansa bile. Bu yüzden bir değeri
`main`'de yazıp bir iş parçacığını `start()` eden, o iş parçacığın içinde değeri bir
`CountDownLatch` ile ikinci bir iş parçacığına ileten bir zincirde, en baştaki yazı en
sondaki okumaya **tek bir zıplamayla değil, halka halka** ulaşır; her halka kendi kuralını
uygular, zincirin bütünü ayrıca kanıtlanmaz. Aşağıdaki ölçümlerin her biri bu zincirin **tek
bir halkasını** — yazan ile okuyan arasındaki doğrudan bağı — izole ediyor.

## Yazanın Bedeli: final Alan ve Güvenli Yayımlama

- **EZ6** — Ölçüm okuyan tarafın kod akışına hiçbir ek adım koymuyor; okuyan yalnızca alanı
  okuyor. Bedel tamamen kurucunun tamamlanma anına yükleniyor.

```java
// GuvenliYayimlama.java — final alanin bedeli kurulum aninda odenir, okuyan hicbir sey yapmaz
public class GuvenliYayimlama {
    static final class Paket {
        final int deger;
        Paket(int deger) { this.deger = deger; }
    }

    public static void main(String[] args) throws InterruptedException {
        Paket kutu = new Paket(5);
        Thread okuyan = new Thread(() -> {
            System.out.println("okuyanin hicbir eklemesi yok, sadece Paket.deger okuyor: " + kutu.deger);
        });
        okuyan.start();
        okuyan.join();
    }
}
```

```
okuyanin hicbir eklemesi yok, sadece Paket.deger okuyor: 5
```

`Paket` kurucu tamamlanana kadar `deger` alanı yazılmamış sayılır; kurucu bittiği an bu alan
**dondurulur**. `okuyan` iş parçacığı `kutu.deger`'i okurken ne bir kilit alıyor ne bir uçucu
alanı denetliyor — hiçbir ek disiplin uygulamıyor, çünkü uygulaması gereken disiplin zaten
**yazan tarafta**, kurucunun içinde tüketildi. Bu derste `kutu` referansı `okuyan`'a
`start()` üzerinden, yani zaten önce-olur bağıntısı kuran bir kanaldan ulaşıyor; `final`
alanın kendine özgü güvencesi bundan da geniştir — kurucu `this` referansını erken kaçırmadığı
sürece, referans hangi kanaldan ulaşırsa ulaşsın alanın doğru değeri görülür. Bu genişletilmiş
güvence burada ayrıca koşturulmuyor: kanıtlamak için referansı **kasıtlı olarak eşleşimsiz**
yayımlamak gerekirdi, ve bu, tam olarak aşağıdaki sınırlayıcı ölçümün kaçındığı türde bir
kurgudur.

Güvencenin bir de koşulu var: kurucu `this` referansını **erken kaçırmamalıdır**. Bir kurucu,
kendi tamamlanması bitmeden `this`'i bir statik alana yazar ya da bir dinleyiciye kaydederse,
başka bir iş parçacığı o nesneyi kurucu hâlâ çalışırken görebilir — ve o anda `final` alan
henüz atanmamış ya da yarım atanmış olabilir. Bu ders böyle bir kaçışı kurmuyor; yalnız
`final` alanın güvencesinin **koşulsuz** olmadığını, kurucunun disiplinine bağlı olduğunu
kaydediyor. `GuvenliYayimlama` bu koşulu sağlıyor: `Paket` kurucusu `this`'i hiçbir yere
yazmıyor, `deger` atandıktan hemen sonra kurucu bitiyor.

## Sekiz Düzenek, Dört Kategori

- **EZ7** — Aşağıdaki altı düzenek `bekle` adlı sınırlı bir döngüyle ölçülüyor: döngü koşulu
  gerçekleşene kadar en çok 200 milyon kez sınanıyor, süresi hiçbir yerde yazdırılmıyor.
  Ölçülen tek şey, koşulun **gerçekleşip gerçekleşmediği** ve gerçekleştiğinde okunan değerdir.

```java
// Yayimlama.java — alti duzenekte yazanin ve okuyanin ayni mekanizmayi kullanmasi yeterli mi
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;

public class Yayimlama {
    static volatile int ucucuDeger = 0;
    static volatile boolean ucucuHazir = false;

    static final Object kilit = new Object();
    static int kilitliDeger = 0;
    static boolean kilitliHazir = false;

    static final AtomicInteger atomikDeger = new AtomicInteger(-1);

    static int joinDeger = 0;
    static int mandalDeger = 0;

    static final ThreadLocal<Integer> ozel = ThreadLocal.withInitial(() -> 0);

    static boolean bekle(BooleanSupplier kosul) {
        for (long i = 0; i < 200_000_000L; i++) if (kosul.getAsBoolean()) return true;
        return false;
    }

    public static void main(String[] args) throws InterruptedException {
        // 1. volatile: yazan ucucu alana yazar, okuyan ayni ucucu alani okur
        Thread t1 = new Thread(() -> { ucucuDeger = 5; ucucuHazir = true; });
        t1.start();
        boolean g1 = bekle(() -> ucucuHazir);
        System.out.println("volatile       : gorundu=" + g1 + " deger=" + ucucuDeger);
        t1.join();

        // 2. synchronized: yazan ve okuyan ayni kilidi kullanir
        Thread t2 = new Thread(() -> { synchronized (kilit) { kilitliDeger = 5; kilitliHazir = true; } });
        t2.start();
        boolean g2 = bekle(() -> { synchronized (kilit) { return kilitliHazir; } });
        int d2; synchronized (kilit) { d2 = kilitliDeger; }
        System.out.println("synchronized   : gorundu=" + g2 + " deger=" + d2);
        t2.join();

        // 3. AtomicInteger: yazan set eder, okuyan get eder
        Thread t3 = new Thread(() -> atomikDeger.set(5));
        t3.start();
        boolean g3 = bekle(() -> atomikDeger.get() != -1);
        System.out.println("AtomicInteger  : gorundu=" + g3 + " deger=" + atomikDeger.get());
        t3.join();

        // 4. join: govde biter, okuyan ancak join'den sonra okur — duz alan, baska hicbir sey yok
        Thread t4 = new Thread(() -> joinDeger = 5);
        t4.start();
        t4.join();
        System.out.println("join           : gorundu=true deger=" + joinDeger);

        // 5. CountDownLatch: yazan countDown eder, okuyan await eder — duz alan
        CountDownLatch mandal = new CountDownLatch(1);
        Thread t5 = new Thread(() -> { mandalDeger = 5; mandal.countDown(); });
        t5.start();
        mandal.await();
        System.out.println("CountDownLatch : gorundu=true deger=" + mandalDeger);
        t5.join();

        // 6. ThreadLocal: her is parcacigi kendi kopyasini gorur, baskasininkini hic gormez
        Thread t6 = new Thread(() -> ozel.set(5));
        t6.start();
        t6.join();
        System.out.println("ThreadLocal    : cagiranin kendi kopyasi=" + ozel.get()
                + " (olusturulanin yazdigi 5 bu kopyada hic yok)");
    }
}
```

```
volatile       : gorundu=true deger=5
synchronized   : gorundu=true deger=5
AtomicInteger  : gorundu=true deger=5
join           : gorundu=true deger=5
CountDownLatch : gorundu=true deger=5
ThreadLocal    : cagiranin kendi kopyasi=0 (olusturulanin yazdigi 5 bu kopyada hic yok)
```

Altı satır da aynı biçimde okunuyor ama gerekçeleri farklı. **`volatile`** ve **`synchronized`**
için hem yazanın hem okuyanın aynı alana ya da aynı kilide dokunması gerekiyor — okuyan başka
bir alanı ya da başka bir kilidi denetleseydi hiçbir garanti kalmazdı; bu ikisi **ikisinin de**
sütununa yazılıyor. **`AtomicInteger`** aynı gerekçeyle üçüncü örnek: yazan `set`, okuyan
`get` çağırıyor, ikisi de aynı nesne üzerinden. **`CountDownLatch`** da öyle — yazan
`countDown`, okuyan `await` çağırmak zorunda; biri eksik olsa bağıntı kurulmaz. **`join`**
farklı bir yükü taşıyor: yazan tarafın yapması gereken **hiçbir ekstra şey yok**, sadece normal
biçimde bitmesi yetiyor; bütün yük okuyanın `join()` çağırmasına biniyor — bu yüzden **yalnız
okuyan** sütununda. `final` alan ise tam tersi: yükün tamamı kurucunun tamamlanmasında, yani
**yalnız yazan** tarafta. **`ThreadLocal`** görünürlük sorusunu hiç sormuyor — her iş
parçacığının kendi kopyası olduğu için ne yazanın ne okuyanın birbirine görünür olması diye
bir beklenti **yok**; bu yüzden **hiçbiri** sütununda. Ötekilerin yedisi bir değeri **paylaşarak**
güvenli hâle getirmenin yollarıydı, `ThreadLocal` ise paylaşmamayı bizzat araç hâline getiriyor
— aynı adı taşıyan bir değişkenin iki iş parçacığında iki ayrı yaşam sürmesi, görünürlük
sorununu çözmüyor, sorudan tamamen çıkarıyor.

| Kategori | Düzenekler | Sayı |
|---|---|---|
| yalnız yazan | `final` alan | 1 |
| yalnız okuyan | `join` | 1 |
| ikisinin de | `volatile`, `synchronized`, `AtomicInteger`, `CountDownLatch` | 4 |
| hiçbiri | senkronizasyonsuz alan, `ThreadLocal` | 2 |

Sekiz düzeneğin **dördü** her iki tarafın da harekete geçmesini istiyor, **biri** yükü tek
başına yazana, **biri** tek başına okuyana yüklüyor, **ikisi** ise görünürlük sorusunun
dışında kalıyor — biri çünkü hiçbir kural onu kurmuyor, öteki çünkü kural kurmaya gerek
duymayacak kadar izole.

`join` ile `CountDownLatch` yan yana konunca ilk bakışta aynı işi yapıyor gibi görünüyorlar,
ama tablodaki yerleri farklı — biri **yalnız okuyan**, öteki **ikisinin de** sütununda. Ayrım
şuradan geliyor: `join`'in kurduğu önce-olur bağıntısı iş parçacığının **kendi doğal sonuna**
bağlıdır, yazan tarafın ayrıca çağırdığı bir yöntem yoktur, gövde nasıl biterse bitsin bağıntı
kurulur. `CountDownLatch` ise böyle bir doğal sona bağlı değildir — sayaç, gövde bitmeden de
bitikten sonra da, yazan tarafın **açıkça** `countDown` çağırmasıyla düşer. Bu fark
`CountDownLatch`'i `join`'den daha esnek kılıyor (bir iş parçacığı bitmeden de ilerleme
sinyali verebiliyor) ama esnekliğin bedeli, yazan tarafa bir çağrı daha yüklemek.

## Sınırlayıcı Ölçüm: Modellenen İki Sınır

- **EZ8** — Bu bölümde hiçbir kod koşturulmuyor. İki iddia da kural referansıyla ve önceki
  kurslara atıfla destekleniyor, ölçülerek değil — çünkü ölçmenin kendisi ya yanıltıcı ya da
  tanım gereği imkânsız.

Tablodaki `hiçbiri` satırının ilk kalemi, senkronizasyonsuz sıradan bir alan, kasıtlı olarak
koşturulmuyor. Bunun nedeni bir tembellik değil, bir sınırdır: senkronizasyonsuz bir alanı
yazan bir iş parçacığı ile onu bir döngüde bekleyerek okuyan bir başka iş parçacığı, aynı
program aynı makinede bile bazen görünürlüğü hemen gösterir, bazen göstermez — sonuç
derleyicinin o an verdiği optimizasyon kararına bağlıdır ve bu ders böyle bir kararı ölçmüyor,
çünkü M08/K04'ün kendi ölçü ekseni bu değildir. Böyle bir döngüyü burada çalıştırıp
"görünmedi" ya da "göründü" yazmak, bir kerelik bir gözlemi genel bir kural gibi sunmak olurdu;
oysa kural zaten yukarıda kuruldu ve bu alan için **hiçbir madde onu kapsamıyor**. Doğru
okuma şudur: bir alan bu listedeki yedi düzenekten birine girmiyorsa, o alanın görünürlüğü
konusunda hiçbir garanti yoktur — ne "görünür" ne "görünmez" garanti edilir, yalnız
**garanti edilmez.**

Bu, kısa süre çalışan bir denemenin **her zaman** görünürlüğü gösterip programcıyı
yanıltabileceği anlamına geliyor. `Yayimlama`'daki `bekle` döngüsü gerçek bir sınama olarak
kalabilmesinin nedeni, denetlediği alanların (`ucucuHazir`, `kilitliHazir`, `atomikDeger`)
üçünün de bir kural tarafından kapsanmasıdır; aynı döngü senkronizasyonsuz bir `boolean` alanı
denetleseydi, muhtemelen yine `gorundu=true` yazdırırdı — çünkü kısa ömürlü bir programda
derleyicinin döngüyü yeniden düzenlemesi için henüz fırsat doğmamış olabilir. Bu görünür
başarı bir kanıt değildir; yalnızca o çalıştırmada kuralın çiğnenmesinin bir bedeli
ödenmemiştir. Anında Derleme dersinin ölçtüğü karar burada da geçerlidir: aynı kaynak, hiç
değişmeden, çalışma zamanındaki bir kararla farklı davranabilir — fark şu ki o ders kararın
komut sayısını ölçtü, bu ders kararın **gözlemlenebilirliği** bozabileceğini yalnızca
belirtiyor.

İkinci sınır `volatile` alanı ilgilendiriyor ve ölçülen görünürlük garantisiyle karıştırılmaya
en açık olanıdır. `volatile`'ın verdiği şey **görünürlük ve sıralamadır**, **atomikliğin**
kendisi değildir. `oku-artır-yaz` üçlemesi kavram olarak M01/K05 İşletim Sistemi Kavramları kursunda
kuruldu; aynı üçleme bir `volatile` alanda da bölünebilir — iki iş parçacığı aynı `volatile`
sayaca `sayac++` yazdığında, her iki taraf da işlemi görüyor olsa bile, biri ötekinin
artırdığı değeri henüz okumadan kendi artırmasını yapabilir ve bir artırma kaybolur. Bu ders
bu iki taraf arasındaki gerçek bir çakışmayı **koşturmuyor**, çünkü sonucu ortama bağlı olurdu
ve kayıp artırmanın kaç kez gerçekleştiği bu dersin ölçü ekseninde hiçbir yere bağlanmıyor.
İddia bir kural olarak kalıyor: `volatile`, tek bir okuma ya da tek bir yazma için görünürlük
sağlar; okuma ve yazmayı birleştiren bir işlem için hiçbir şey sağlamaz. Bunun asıl aracı,
sonraki derste ölçülen `AtomicInteger`'ın `karşılaştır-ve-ata` yöntemidir.

## Özet

- Görünürlük otomatik değildir; bir yazının bir okumaya görünmesi, ikisi arasında dilin tanıdığı
  bir önce-olur bağıntısının kurulmuş olmasına bağlıdır.
- Sekiz yayımlama düzeneğinden **dördü** (`volatile`, `synchronized`, `AtomicInteger`,
  `CountDownLatch`) hem yazanın hem okuyanın harekete geçmesini istiyor, **biri** (`final`
  alan) yükü yalnız yazana, **biri** (`join`) yükü yalnız okuyana yüklüyor, **ikisi**
  (senkronizasyonsuz alan, `ThreadLocal`) görünürlük sorusunun dışında kalıyor.
- `final` alanın bedeli kurulum anında ödenir: kurucu tamamlanınca okuyan taraf hiçbir ek
  disiplin uygulamadan doğru değeri görür.
- Senkronizasyonsuz bir alanın görünürlük kusuru koşturularak gösterilmez; bu ders bunu
  kasıtlı olarak modelliyor, çünkü sonucu ortama bağlı olurdu.
- `volatile` görünürlük ve sıralama verir, atomiklik vermez; oku-artır-yaz üçlemesi bir
  `volatile` alanda da bölünebilir ve bu ders bunu koşturarak değil kural olarak anlatıyor.

## Sonraki Adım

Bu derste `synchronized` bloğu tabloya yalnız bir satır olarak girdi ve okuyanla yazanın
**aynı** kilidi kullanması gerektiği söylendi, ama "aynı kilit" ifadesinin kendisi hiç
sorgulanmadı — `kilit` bu derste tek bir statik alandı ve iki iş parçacığının aynı nesneyi
kullandığı baştan verilmişti. Sıradaki ders bu varsayımı kaldırıyor ve izleyici kilidinin
kime ait olduğunu ölçüyor: aynı senkronize yöntem iki ayrı örnekte çağrıldığında kaç ayrı
kilit devreye giriyor, ve bir iş parçacığı kendi tuttuğu kilide yeniden girdiğinde bu kaç kez
sayılıyor?
