Multithreading Temelleri
Bilgisayarın aynı anda birden fazla iş yapabilmesinin sırrı: thread'ler. Müzik dinlerken kod yazabiliyorsun, dosya indirirken tarayıcıda gezinebiliyorsun. Bunların hepsi thread'ler sayesinde. Java'da multithreading hem güçlü hem de dikkat gerektiren bir konu.
Thread Nedir?
Bir restoran düşün. Mutfakta tek aşçı varsa, bir yemek bitene kadar diğer siparişler bekler. Ama 5 aşçı varsa, 5 sipariş aynı anda hazırlanır. Her aşçı bir thread.
Process = restoran (program). Thread = aşçı (programın içindeki iş parçacığı).
Her Java programı en az 1 thread ile başlar: main thread. Ama daha fazla thread oluşturarak işleri paralel yapabilirsin.
public class MainThread {
public static void main(String[] args) {
// Bu kod main thread'de çalışır
System.out.println("Thread: " + Thread.currentThread().getName());
// Çıktı: Thread: main
}
}Thread Oluşturma — İki Yol
Yol 1: Thread Sınıfını Extend Etmek
public class Sayici extends Thread {
private String isim;
public Sayici(String isim) {
this.isim = isim;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(isim + ": " + i);
try {
Thread.sleep(500); // 500ms bekle
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}Sayici s1 = new Sayici("Ali");
Sayici s2 = new Sayici("Veli");
s1.start(); // Yeni thread başlat
s2.start(); // Başka bir thread başlatOlası çıktı (her çalışmada farklı olabilir):
Ali: 1
Veli: 1
Ali: 2
Veli: 2
Veli: 3
Ali: 3
...Yol 2: Runnable Interface Implement Etmek (Tercih Edilen)
public class Sayici implements Runnable {
private String isim;
public Sayici(String isim) {
this.isim = isim;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(isim + ": " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}Thread t1 = new Thread(new Sayici("Ali"));
Thread t2 = new Thread(new Sayici("Veli"));
t1.start();
t2.start();Lambda ile (En Kısa Yol)
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("Thread-1: " + i);
}
});
Thread t2 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("Thread-2: " + i);
}
});
t1.start();
t2.start();💡 Neden Runnable tercih edilir? Java'da tek sınıf extend edebilirsin ama birden fazla interface implement edebilirsin.
Thread'i extend edersen başka sınıf extend edemezsin.Runnableimplement etmek daha esnek.
start() vs run() — Kritik Fark!
Bu, en sık yapılan hatalardan biri:
Thread t = new Thread(() -> {
System.out.println("Çalışan thread: " + Thread.currentThread().getName());
});
// DOĞRU — yeni thread oluşturur ve run()'ı o thread'de çağırır
t.start();
// Çıktı: Çalışan thread: Thread-0
// YANLIŞ — yeni thread oluşturmaz, run()'ı mevcut thread'de çağırır
t.run();
// Çıktı: Çalışan thread: mainstart() → JVM yeni bir thread oluşturur, run() metodunu o thread'de çağırır. run() → Normal metod çağrısı, yeni thread yok. Main thread'de çalışır.
⚠️ Asla `run()` çağırma, her zaman `start()` kullan!
run()çağırmak multithreading değil, sıradan metod çağrısıdır.
Bir thread'e start() sadece bir kez çağrılabilir:
Thread t = new Thread(() -> System.out.println("Çalıştım"));
t.start(); // ✅
t.start(); // ❌ IllegalThreadStateException!Thread.sleep() — Bekletme
Mevcut thread'i belirli süre uyutur:
System.out.println("Başladı");
try {
Thread.sleep(2000); // 2 saniye bekle
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Uyku bölündü!");
}
System.out.println("Bitti"); // 2 saniye sonrasleep() statik metod — her zaman mevcut thread'i uyutur. Başka bir thread'i uyutamazsın.
// YANLIŞ ANLAMA — t1'i uyutmaz, mevcut thread'i uyutur!
t1.sleep(1000); // Aslında Thread.sleep(1000) ile aynıjoin() — Thread'in Bitmesini Bekle
Bir thread'in bitmesini beklemek için join() kullanılır:
Thread t1 = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("t1 bitti");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread t2 = new Thread(() -> {
System.out.println("t2 bitti");
});
t1.start();
t2.start();
System.out.println("Main: t1'i bekliyorum...");
t1.join(); // t1 bitene kadar main thread bekler
System.out.println("Main: t1 bitti, devam ediyorum");
t2.join(); // t2 bitene kadar bekle
System.out.println("Main: Herkes bitti!");Çıktı:
Main: t1'i bekliyorum...
t2 bitti
t1 bitti
Main: t1 bitti, devam ediyorum
Main: Herkes bitti!Zaman Aşımlı Join
t1.join(5000); // Maksimum 5 saniye bekle
if (t1.isAlive()) {
System.out.println("t1 hâlâ çalışıyor — beklemeyi bıraktım");
}Thread States — Thread Durumları
Bir thread'in yaşam döngüsünde farklı durumlar vardır:
NEW → RUNNABLE → RUNNING → (BLOCKED/WAITING/TIMED_WAITING) → TERMINATEDThread t = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
System.out.println(t.getState()); // NEW — oluşturuldu ama başlatılmadı
t.start();
System.out.println(t.getState()); // RUNNABLE — çalışmaya hazır
Thread.sleep(100);
System.out.println(t.getState()); // TIMED_WAITING — sleep'te
t.join();
System.out.println(t.getState()); // TERMINATED — bitti| Durum | Açıklama |
|---|---|
| NEW | Thread oluşturuldu ama start() çağrılmadı |
| RUNNABLE | Çalışmaya hazır, CPU bekliyor |
| BLOCKED | Monitor lock bekliyor (synchronized) |
| WAITING | Süresiz bekleme (wait, join) |
| TIMED_WAITING | Süreli bekleme (sleep, join(ms)) |
| TERMINATED | Çalışması bitti |
Thread İsimlendirme
Thread'lere isim vermek debug için çok önemli:
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " çalışıyor");
}, "VeriIsleyici");
t.start(); // VeriIsleyici çalışıyor
// Veya sonradan isim ver
Thread t2 = new Thread(() -> { /* ... */ });
t2.setName("LogYazici");
t2.start();Hata ayıklarken thread dump'ta isimler görünür. "Thread-0", "Thread-1" yerine anlamlı isimler hayat kurtarır.
Pratik Örnek — Paralel İndirme Simülasyonu
public class ParalelIndirme {
static void dosyaIndir(String dosyaAdi, int sureSaniye) {
System.out.println(dosyaAdi + " indiriliyor...");
try {
Thread.sleep(sureSaniye * 1000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
System.out.println(dosyaAdi + " tamamlandı! (" + sureSaniye + "sn)");
}
public static void main(String[] args) throws InterruptedException {
long baslangic = System.currentTimeMillis();
Thread t1 = new Thread(() -> dosyaIndir("video.mp4", 3), "İndirici-1");
Thread t2 = new Thread(() -> dosyaIndir("muzik.mp3", 2), "İndirici-2");
Thread t3 = new Thread(() -> dosyaIndir("belge.pdf", 1), "İndirici-3");
t1.start();
t2.start();
t3.start();
// Hepsinin bitmesini bekle
t1.join();
t2.join();
t3.join();
long sure = System.currentTimeMillis() - baslangic;
System.out.println("Toplam süre: " + sure + "ms");
// ~3 saniye (sıralı olsa 6 saniye olurdu)
}
}Çıktı:
video.mp4 indiriliyor...
muzik.mp3 indiriliyor...
belge.pdf indiriliyor...
belge.pdf tamamlandı! (1sn)
muzik.mp3 tamamlandı! (2sn)
video.mp4 tamamlandı! (3sn)
Toplam süre: 3015ms3 indirme paralel çalıştığı için toplam süre, en uzun indirme kadar (3 saniye). Sıralı olsa 1+2+3 = 6 saniye olurdu.
Thread Güvenliği — İlk Bakış
Birden fazla thread aynı veriye eriştiğinde sorunlar çıkar:
public class Sayac {
private int deger = 0;
public void artir() {
deger++; // Bu satır THREAD-SAFE DEĞİL!
}
public int getDeger() {
return deger;
}
}Sayac sayac = new Sayac();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) sayac.artir();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) sayac.artir();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Beklenen: 20000");
System.out.println("Gerçek: " + sayac.getDeger()); // 20000'den az olabilir!Neden? deger++ aslında 3 adım:
deger'i oku1 ekle
Geri yaz
İki thread aynı anda 1. adımı yaparsa, ikisi de aynı değeri okur ve biri kaybolur. Buna race condition denir.
Bu sorunu çözmek için synchronized, Lock, Atomic gibi araçlar var. İlerleyen derslerde detaylıca göreceğiz.
⚠️ Shared mutable state = tehlike! Birden fazla thread'in aynı değiştirilebilir veriye erişmesi, multithreading'in en büyük tuzağı. Mümkünse immutable veri yapıları kullan.
Thread ile Dikkat Edilecekler
Exception Handling
Thread içindeki exception, main thread'e otomatik iletilmez:
Thread t = new Thread(() -> {
throw new RuntimeException("Hata!"); // Bu exception kaybolur
});
t.start();
// Çözüm: UncaughtExceptionHandler
t.setUncaughtExceptionHandler((thread, ex) -> {
System.out.println(thread.getName() + " hata: " + ex.getMessage());
});Thread Sayısı
Thread oluşturmak bedava değil. Her thread bellek kullanır (varsayılan ~1MB stack). 10.000 thread oluşturursan ~10GB bellek harcar.
// KÖTÜ — çok fazla thread
for (int i = 0; i < 10000; i++) {
new Thread(() -> { /* iş yap */ }).start(); // 10.000 thread!
}
// İYİ — thread pool kullan (B12 Thread Pool dersinde detaylı)
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10000; i++) {
pool.submit(() -> { /* iş yap */ }); // 10 thread, 10.000 iş
}
pool.shutdown();Özet
Thread, bir programın içinde bağımsız çalışan iş parçacığı — paralel iş yapmayı sağlar
Thread oluşturmak için Runnable implement et (tercih edilen) veya Thread extend et
`start()` ile thread başlatılır —
run()doğrudan çağrılmaz!`sleep()` mevcut thread'i uyutur, `join()` başka thread'in bitmesini bekler
Thread'ler NEW → RUNNABLE → RUNNING → TERMINATED yaşam döngüsünden geçer
Shared mutable state race condition yaratır — sonraki derslerde çözümünü göreceğiz
AI Asistan
Sorularını yanıtlamaya hazır