---
title: 'İş Parçacıkları ve Yaşam Döngüsü'
source: 'https://academia.sh/tr/kurslar/jvm-ve-eszamanlilik/is-parcaciklari-ve-yasam-dongusu'
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'
---

# İş Parçacıkları ve Yaşam Döngüsü

Aynı gövde run() ile çağıranda, start() ile oluşturulan bir iş parçacığında koşuyor; fark tek bir yöntem adı. Üç kesme kurulumundan yalnız biri gövdeyi durduruyor, kalan ikisi kesme çağrısı olsun olmasın aynı adıma ulaşıyor.

Anında Derleme dersi şu sonuçla bitti: sınıf dosyasında bulunmayan bir optimizasyon kararını
veren taraf programın kendisi değildi, ama kodu koşturan iş parçacıklarını oluşturan yine
programdı. Bu ders oradan başlıyor. Sınıf dosyasını kim yüklüyor, kararı kim veriyor, profili
kim üretiyor sorularının hepsi bir gövdenin **zaten koştuğunu** varsayıyordu. Şimdi bir adım
geri gidiliyor: bir iş parçacığı nasıl var ediliyor, ve az önce yazılmış olan gövdesini hangi
taraf koşturuyor?

Sorunun yanıtı çağrı yazımında görünmüyor. `Runnable` gövdesi tek bir yerde tanımlanır ve iki
ayrı yöntemle tetiklenebilir; ikisi de aynı satırı çalıştırır, ikisi de derleyici için geçerli
bir çağrıdır. Bu dersin ölçtüğü ilk fark — kursun ortak çekirdeğinin de ilk iki satırı — tam
olarak burada duruyor.

Kursun sorduğu soru iki parçadan oluşuyor: bir adımı hangi iş parçacığı koşturuyor, ve o iş
parçacığını kim var etti? Bu ders yalnız ikinci parçaya, var etmenin kendisine bakıyor. Bir
program çalışırken zaten en az bir iş parçacığı vardır — `main` yöntemini koşturan iş
parçacığı — ve program başka hiçbir iş parçacığı istemezse bu tek başına yeterlidir. Yeni bir
iş parçacığı istemek gövdeyi ayrı bir akışa taşır; o akış artık **çağıranın** akışı değildir
ve kendi başına başlar, kendi başına biter. Aşağıdaki iki bölüm bu var etme ve bitirme
sürecini, ortadaki üçüncü bir soruyla birlikte ölçüyor: yarım kalmış bir iş parçacığına
"artık dur" demenin gövdeye ne yaptırdığı.

## Aynı Gövde, İki Koşum Yolu

- **EZ1** — Kim koşturduğunu söyleyen etiket, iş parçacığının kendisinden değil, ayrı tutulan
  bir kayıttan okunur; bir iş parçacığı `Kosum.tanit` ile kaydolmadıysa `bilinmeyen` sayılır.
- **EZ2** — `Govdeler.main`'i çalıştıran iş parçacığı baştan `"cagiran"` diye kaydedilir; bu
  dersin geri kalanında "çağıran" hep bu iş parçacığıdır.

```java
// Kosum.java — bir govdeyi hangi is parcaciginin kosturdugunu etiketle soyler
import java.util.*;

public class Kosum {
    static final Map<Thread, String> etiketler =
            Collections.synchronizedMap(new IdentityHashMap<>());

    static void tanit(String etiket) { etiketler.put(Thread.currentThread(), etiket); }

    static String kim() {
        String e = etiketler.get(Thread.currentThread());
        return e == null ? "bilinmeyen" : e;
    }

    static void gozle(String nokta) {
        System.out.printf("%-32s %s%n", nokta, kim());
    }
}
```

`Kosum`, iş parçacığının adını, kimlik karmasını ya da önceliğini hiç okumaz — bunların hepsi
ortama bağlı veridir. Tek okuduğu şey, `tanit` ile önceden yazılmış bir etikettir; kimlik
sorusu böylece bizim verdiğimiz ada indirgenir.

```java
// Govdeler.java — ayni govde run() ile cagiranda, start() ile olusturulan is parcacikinda kosuyor
public class Govdeler {
    public static void main(String[] args) throws InterruptedException {
        Kosum.tanit("cagiran");
        Runnable govde = () -> Kosum.gozle("govde");

        // 1. dogrudan cagri: run() adi baska bir is parcacigi baglamaz, cagiran kosturur
        new Thread(govde).run();

        // 2. start(): govdeyi olusturulan is parcacigi kosturur
        Thread t = new Thread(() -> { Kosum.tanit("olusturulan"); govde.run(); });
        t.start();
        t.join();
    }
}
```

```
govde                            cagiran
govde                            olusturulan
```

İki satır da aynı `govde` referansını çalıştırıyor, ikisi de `Runnable.run()` çağırıyor. Fark
kimin `run()` dediğidir: birinci satırda `new Thread(govde)` nesnesinin `run()`'ı doğrudan
çağrılıyor — bu, sıradan bir yöntem çağrısıdır, yeni bir iş parçacığı **oluşturmaz**, sadece
gövdeyi çağıranın kendi yığıtında çalıştırır. İkinci satırda `start()` çağrılıyor; bu,
çalışma zamanına yeni bir iş parçacığı isteği yollar ve gövde o iş parçacığında koşar. `t.join()`
olmasaydı `main`'in gövdenin bitişini beklediğini hiçbir şey garanti etmezdi — bu köprü
aşağıdaki sınırlayıcı ölçümde ayrıca ele alınıyor.

`new Thread(govde)` ifadesinin kendisi, henüz hiçbir şey **koşturmuyor**. İki koşum yolunda da
önce bir `Thread` nesnesi kuruluyor; nesne kurulduğu an yalnızca bir kayıt taşıyor, gövdeyi
kimin çalıştıracağına dair henüz bir taahhüt yok. Birinci satırda o nesne hiç `start()`
edilmeden `run()` ile çağrılıyor — nesne var ama iş parçacığı hiç talep edilmemiş oluyor,
gövde doğrudan çağıranın kod akışına giriyor. İkinci satırda ise `start()` çağrısı bu
taahhüdü veriyor: çalışma zamanından yeni bir iş parçacığı istiyor, ve gövde artık o isteğin
karşılığı olan iş parçacığında koşuyor. Aradaki fark, çağrının kendisinin bir iş parçacığı
**var ettiği** ile var etmediği arasındaki farktır — ve bu fark, iki satırın ikisinde de aynı
görünen `run()` çağrısının arkasında saklı kalıyor.

## Yaşam Döngüsü: Belirlenimci Gözlemler

- **EZ3** — `isAlive()` iş parçacığının kendisinden hiçbir kimlik verisi okumaz, yalnız
  "başlatıldı ve henüz bitmedi mi" sorusuna `true`/`false` döner; bu, ortama bağlı değildir.

Bir `Thread` nesnesi `start()` çağrılmadan önce **yeni**dir, `start()` sonrası ve bitmeden
önce **çalışır durumdadır**, `join()`'in döndüğü an ise kesin olarak **bitmiştir**. Üç durum
da `isAlive()` ile belirlenimci biçimde gözlenir; aradaki geçişler zamanlamaya değil, hangi
yöntemin çağrıldığına bağlıdır. `isAlive()`'ın kendisi de iş parçacığından hiçbir ortama bağlı
veri okumuyor — döndürdüğü tek bilgi bir mantıksal değer, ve o değer hangi yöntemlerin daha
önce çağrıldığından belirleniyor, makinenin o an ne kadar meşgul olduğundan değil.

```java
// YasamDongusu.java — durumlar belirlenimci gozlenir: baslamadan once, calisirken, bittikten sonra
import java.util.concurrent.CountDownLatch;

public class YasamDongusu {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch baslasin = new CountDownLatch(1);

        Thread t = new Thread(() -> {
            try { baslasin.await(); } catch (InterruptedException e) { }
        });

        System.out.println("baslamadan once  isAlive: " + t.isAlive());
        t.start();
        System.out.println("start sonrasi    isAlive: " + t.isAlive());
        baslasin.countDown();
        t.join();
        System.out.println("join sonrasi     isAlive: " + t.isAlive());

        try {
            t.start();
            System.out.println("ikinci start     : basarili");
        } catch (IllegalThreadStateException e) {
            System.out.println("ikinci start     : " + e.getClass().getSimpleName());
        }
    }
}
```

```
baslamadan once  isAlive: false
start sonrasi    isAlive: true
join sonrasi     isAlive: false
ikinci start     : IllegalThreadStateException
```

`baslasin` gövdeyi bir `CountDownLatch` üzerinde bekletiyor; böylece `start()` döndükten sonra
gövdenin henüz bitmediği, `isAlive()` ile **koşuma bırakmadan** garanti ediliyor. `join()`
çağrısı iki işi birden yapıyor: hem çağıranı gövde bitene kadar durduruyor hem de dönüşünden
sonra `isAlive()`'ın `false` olacağını kesinleştiriyor — `join`, bu dersin sıralama aracıdır.
Son satır ayrı bir kuralı gösteriyor: bir `Thread` nesnesi yalnız **bir kez** başlatılabilir;
ikinci `start()` çağrısı, nesnenin durumundan bağımsız olarak her zaman
`IllegalThreadStateException` fırlatır.

Bu kısıtlamanın nedeni yaşam döngüsünün kendisidir. `start()`, bir `Thread` nesnesini
**yeni** durumdan çıkarıp çalışma zamanına bir kez teslim eder; teslimden sonra o nesne bir
daha hiç **yeni** duruma dönmez, bitince kalıcı olarak sona ermiş sayılır. Bir nesneyi ikinci
kez başlatmaya izin vermek, aynı iş parçacığı kimliğinin iki ayrı koşum boyunca yeniden
kullanılması demek olurdu; çerçeve bunun yerine her koşum için taze bir `Thread` nesnesi
kurulmasını zorunlu kılar. Bitmiş bir gövdeyi yeniden çalıştırmak isteyen kod, aynı `Runnable`
gövdesini **yeni** bir `Thread` nesnesine sararak yapar — nesne değişir, gövde değişmez.

## Kesme: Bir İstek, Cevap Gövdede

- **EZ4** — Kesme bir buyruk değildir; `Thread.interrupt()` yalnız bir bayrağı işaretler ya da
  engelleyen bir çağrıda istisna düşürür. Gövdenin bu isteğe **ne yapacağı**, gövdenin kendi
  kodunda yazılıdır — çerçevenin belirlediği bir şey değildir.

Üç kurulum aynı deseni paylaşıyor: bir iş parçacığı başlar, kendini `CountDownLatch` ile
kaydettirir, sonra kesme isteği (isteğe bağlı olarak) gönderilir, en son `join()` ile
tamamlanması beklenir. Değişen tek şey gövdenin kesmeye **nasıl davrandığıdır**.

Birinci gövdenin döngü koşulu `Thread.currentThread().isInterrupted()` çağırıyor; bu, bir
örnek yöntemidir ve bayrağı okuduktan sonra **temizlemez**. Aynı soruyu soran ama bayrağı okur
okumaz sıfırlayan statik bir karşılığı da var; o karşılık burada kasıtlı olarak kullanılmıyor,
çünkü döngüden çıktıktan sonraki `sonuc.append` satırı hâlâ "bu iş parçacığı kesme istedi"
bilgisine ihtiyaç duyabilirdi — sıfırlayan biçim bayrağı ilk okumada silip bu bilgiyi
döngünün dışına taşımazdı. Hangi biçimin kullanılacağı da gövdenin kararıdır — çerçeve ikisini
de sunar, sonrasında bayrağın gerekip gerekmediğine göre seçim programcıya kalır.

```java
// Kesme.java — kesme bir istektir: govde onu gorebilir ama durup durmamaya kendi kodu karar verir
import java.util.concurrent.CountDownLatch;

public class Kesme {
    static String bayragiDenetleyerekDur(boolean kesEt) throws InterruptedException {
        CountDownLatch basladi = new CountDownLatch(1);
        StringBuilder sonuc = new StringBuilder();
        Thread t = new Thread(() -> {
            basladi.countDown();
            while (!Thread.currentThread().isInterrupted()) { }
            sonuc.append("bayrak gorulunce dongudan cikti");
        });
        t.start();
        basladi.await();
        if (kesEt) t.interrupt();
        t.join();
        return sonuc.toString();
    }

    static String uykudaYakalar(boolean kesEt) throws InterruptedException {
        CountDownLatch basladi = new CountDownLatch(1);
        StringBuilder sonuc = new StringBuilder();
        Thread t = new Thread(() -> {
            basladi.countDown();
            try { Thread.sleep(80); sonuc.append("uyku tamamlandi, "); }
            catch (InterruptedException e) { sonuc.append("istisna yakalandi, "); }
            sonuc.append("ayni sonraki adima gecti");
        });
        t.start();
        basladi.await();
        if (kesEt) t.interrupt();
        t.join();
        return sonuc.toString();
    }

    static String bayragiHicSormaz(boolean kesEt) throws InterruptedException {
        CountDownLatch basladi = new CountDownLatch(1);
        StringBuilder sonuc = new StringBuilder();
        Thread t = new Thread(() -> {
            basladi.countDown();
            long toplam = 0;
            for (long i = 0; i < 20_000_000L; i++) toplam += i;
            sonuc.append("dongu tamamlandi (toplam sifirdan buyuk: " + (toplam > 0) + ")");
        });
        t.start();
        basladi.await();
        if (kesEt) t.interrupt();
        t.join();
        return sonuc.toString();
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("1. bayragi denetleyen govde, kesme cagrisiyla:");
        System.out.println("   " + bayragiDenetleyerekDur(true));
        System.out.println();

        System.out.println("2. uykuda yakalayan govde, kesme cagrisiyla:");
        System.out.println("   " + uykudaYakalar(true));
        System.out.println("2. ayni govde, kesme cagrisi OLMADAN:");
        System.out.println("   " + uykudaYakalar(false));
        System.out.println();

        System.out.println("3. bayragi hic sormayan govde, kesme cagrisiyla:");
        System.out.println("   " + bayragiHicSormaz(true));
        System.out.println("3. ayni govde, kesme cagrisi OLMADAN:");
        System.out.println("   " + bayragiHicSormaz(false));
    }
}
```

```
1. bayragi denetleyen govde, kesme cagrisiyla:
   bayrak gorulunce dongudan cikti

2. uykuda yakalayan govde, kesme cagrisiyla:
   istisna yakalandi, ayni sonraki adima gecti
2. ayni govde, kesme cagrisi OLMADAN:
   uyku tamamlandi, ayni sonraki adima gecti

3. bayragi hic sormayan govde, kesme cagrisiyla:
   dongu tamamlandi (toplam sifirdan buyuk: true)
3. ayni govde, kesme cagrisi OLMADAN:
   dongu tamamlandi (toplam sifirdan buyuk: true)
```

Birinci kurulumun döngüsü **yalnız** kesme bayrağına bakıyor; kesme çağrısı gelmeseydi bu
döngünün başka hiçbir çıkış yolu yoktu — kodun kendisi bunu gösteriyor, bu yüzden o kurulum
kesmesiz çalıştırılmıyor. İkinci ve üçüncü kurulumlar ise **iki kez**, biri kesme çağrısıyla
biri çağrısız, koşturuldu ve ikisinde de aynı sonuca ulaştı: `Thread.sleep` içindeki yakalama
bloğu istisnayı yutup bir sonraki satıra geçiyor, sayaç döngüsü ise bayrağı hiç okumadığı için
kesmenin varlığından habersiz tamamlanıyor. Üç kurulumun **yalnız biri**, kesme isteği
gelmeden gövdesini hiç bitiremiyor; kalan ikisi kesme isteğini görsün ya da görmesin aynı
adıma ulaşıyor. Kesmenin kendisi bir davranış dayatmıyor — davranışı gövdenin yazarı seçiyor.

Aradaki ayrım tek tek bu üç örneğe özgü değil, genelleşen bir kuraldır. Standart kitaplığın
**engelleyen** çağrıları — `Thread.sleep`, nesne kilidinde beklerken çağrılan bekleme yöntemi,
`join`, eşzamanlılık paketindeki bekleyen çağrılar — kesme bayrağını kendileri denetler ve
bayrak işaretliyken (ya da işaretlendiği an) `InterruptedException` fırlatıp bayrağı
**temizler**. Bunun dışındaki her kod — bir hesaplama döngüsü, bir dosyaya yazma, bir ağ
soketi okuma — bayrağı kendiliğinden denetlemez; denetlemek isteyen gövde bunu `birinci
kurulum`daki gibi kendi elleriyle yazmak zorundadır. İkinci kurulum bu ayrımın ortasında
duruyor: `sleep` bayrağı görüyor ve istisna fırlatıyor, ama gövde o istisnayı yakalayıp hiçbir
şey yapmadan yoluna devam ediyor. İstisnanın fırlatılması bir garantidir; ona **cevap vermek**
gövdenin sorumluluğudur ve bu sorumluluk çoğu zaman boş bir `catch` bloğunda kayboluyor.

## Sınırlayıcı Ölçüm: start()'ın Dönmesi Sıra Vermez

`Govdeler.main`'deki `t.start()` çağrısı döndüğünde, gövdenin ilk satırının çalışmış olduğunu
hiçbir şey garanti etmez. `start()`'ın görevi yeni bir iş parçacığı **istemektir**; o isteğin
ne zaman yerine getirileceği çağıranın bilgisi dışındadır. Bu yüzden `Govdeler.main` içinde
`t.join()` çağrılmadan önce gövdenin çıktısını okumaya çalışan bir satır eklenseydi, o satırın
gövdenin çıktısından önce mi sonra mı çalışacağı **kurguya bağlanmamış bir gözlem** olurdu ve
ölçüm sayılmazdı — tıpkı bu dersin ortak tanımının kör noktasında yazılana benzer biçimde,
"olmadı" ile "sırası kurulmadı" birbirine karıştırılmış olurdu. `YasamDongusu` ve `Kesme`
derslerindeki her kurulum bu yüzden bir `CountDownLatch` ile açılıyor: gövde önce kendini
kaydettiriyor, çağıran ancak o kayıttan sonra bir sonraki adıma geçiyor. Sıra çizelgeleyiciye
bırakılmıyor; `await`, `join` ve kayıt noktalarıyla elle kuruluyor.

Bu sınır, ölçümün neden hep aynı iskeleti taşıdığını da açıklıyor. `Kesme.bayragiDenetleyerekDur`
gövdesi kendini `basladi.countDown()` ile kaydettirmeden önce ana iş parçacığı `basladi.await()`
üzerinde bekliyor; kesme isteği ancak bu kayıttan sonra gönderiliyor. Kayıt satırı atlanıp
`t.interrupt()` doğrudan `t.start()`'ın hemen ardından çağrılsaydı, isteğin gövde henüz
`while` döngüsüne girmeden mi girdikten sonra mı geldiği belirsiz kalırdı — sonuç değişmezdi
(döngü er ya da geç bayrağı görüp çıkardı), ama **hangi turda** çıktığı ölçülemezdi. Bu
derste öyle bir tur sayısı hiç iddia edilmiyor; iddia yalnız **çıktı mı çıkmadı mı**
sorusuyla sınırlı tutuluyor, çünkü sınırın kendisi tam olarak budur: ölçülebilecek olan,
kurgunun deterministik kıldığı kadarıdır.

## Özet

- Aynı `Runnable` gövdesi `run()` ile çağrılırsa çağıranda, `start()` ile çağrılırsa
  oluşturulan bir iş parçacığında koşar; fark çağrı yazımında tek bir yöntem adıdır.
- `isAlive()` bir iş parçacığının başlamadan önce, çalışırken ve `join()` sonrası durumunu
  belirlenimci biçimde gözler; ikinci `start()` çağrısı nesnenin durumundan bağımsız olarak
  her zaman `IllegalThreadStateException` fırlatır.
- Kesme bir istektir, bir buyruk değildir: üç kurulumun yalnız biri (bayrağı döngü koşulunda
  denetleyen) kesme isteği olmadan gövdesini hiç bitiremiyor; kalan ikisi isteği görsün ya da
  görmesin aynı sonraki adıma ulaşıyor.
- `start()`'ın dönmesi, gövdenin ilk satırının koştuğu anlamına gelmez; iki olay arasında
  sıra kurulmamıştır ve bu, kurgusuz gözlenemez — sıra `CountDownLatch` ve `join` ile elle
  kurulur.

## Sonraki Adım

Bu derste "kim koşturuyor" sorusu yanıtlandı ve kesme isteğine verilecek yanıtın gövdenin
kendi kodunda yazıldığı görüldü. Ama `Kesme` derslerinde her kurulum, gövdenin yazdığı bir
`StringBuilder`'ı ve bir sayacı okurken hiçbir eşitleme kullanmadı, çünkü değeri hep aynı iş
parçacığı hem yazdı hem okudu — `basladi.await()` ile `t.join()` yalnız **sırayı** kurdu,
görünürlüğü sağlayan gerçek mekanizma her ikisinin de kendi içinde zaten taşıdığı bir
garantiydi ve bu derste hiç adı konmadı. Sıradaki ders bu rahatlığı kaldırıyor: bir değeri
yazan iş parçacığı ile okuyan iş parçacığı farklı olduğunda ne garanti ediliyor, ve bu
garantiyi hangi taraf — yalnız yazan mı, yalnız okuyan mı, ikisi de mi — kurmak zorunda?
