Thread Pool ve ExecutorService
Her iş için yeni thread oluşturmak, her yolculuk için yeni araba almak gibi. Pahalı, verimsiz ve yönetilmesi zor. Bunun yerine bir thread havuzu (pool) oluşturup, işleri bu havuzdaki thread'lere dağıtmak çok daha mantıklı.
Neden Thread Pool?
Doğrudan thread oluşturmanın sorunları:
Thread oluşturma maliyetli — OS düzeyinde kaynak tahsisi gerekir
Bellek tüketimi — her thread ~1MB stack bellek kullanır
Sınırsız thread — 10.000 istek = 10.000 thread = OutOfMemoryError
Yönetim zorluğu — her thread'i takip etmek, kapatmak, hata yönetimi
Thread pool bunları çözer:
// KÖTÜ — her iş için yeni thread
for (int i = 0; i < 1000; i++) {
new Thread(() -> isYap()).start(); // 1000 thread!
}
// İYİ — thread pool
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
pool.submit(() -> isYap()); // 10 thread, 1000 iş
}
pool.shutdown();10 thread, 1000 işi sırayla üstlenir. Bir thread işini bitirince kuyruktan yeni iş alır.
ExecutorService — Thread Pool Arayüzü
ExecutorService, thread pool'un ana arayüzüdür. Executors fabrika sınıfı ile oluşturulur.
Fixed Thread Pool
Sabit sayıda thread. En yaygın kullanılan.
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i = 1; i <= 10; i++) {
final int isNo = i;
pool.submit(() -> {
String thread = Thread.currentThread().getName();
System.out.println(thread + " → İş #" + isNo + " başladı");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(thread + " → İş #" + isNo + " bitti");
});
}
pool.shutdown(); // Yeni iş kabul etme, mevcut işleri bitirÇıktı (yaklaşık):
pool-1-thread-1 → İş #1 başladı
pool-1-thread-2 → İş #2 başladı
pool-1-thread-3 → İş #3 başladı
pool-1-thread-4 → İş #4 başladı
(1 saniye sonra)
pool-1-thread-1 → İş #1 bitti
pool-1-thread-1 → İş #5 başladı ← Thread-1 yeni iş aldı
pool-1-thread-2 → İş #2 bitti
pool-1-thread-2 → İş #6 başladı ← Thread-2 yeni iş aldı
...4 thread, 10 işi sırayla halleder.
Cached Thread Pool
İhtiyaç oldukça thread oluşturur, boştakileri 60 saniye sonra kapatır.
ExecutorService pool = Executors.newCachedThreadPool();
// Kısa süreli, çok sayıda iş için uygun
// DİKKAT: Sınırsız thread oluşturabilir!Single Thread Executor
Tek thread'li pool. İşler sırayla çalışır.
ExecutorService pool = Executors.newSingleThreadExecutor();
// Sıralı iş garantisi — FIFOScheduled Thread Pool
Zamanlı ve periyodik işler için.
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
// 3 saniye sonra çalıştır
scheduler.schedule(() -> {
System.out.println("3 saniye sonra çalıştım!");
}, 3, TimeUnit.SECONDS);
// Her 2 saniyede bir çalıştır (1 saniye gecikme ile başla)
scheduler.scheduleAtFixedRate(() -> {
System.out.println("Periyodik iş: " + LocalTime.now());
}, 1, 2, TimeUnit.SECONDS);
// 5 saniye sonra scheduler'ı kapat
scheduler.schedule(() -> scheduler.shutdown(), 10, TimeUnit.SECONDS);Pool Karşılaştırma Tablosu
| Pool Tipi | Thread Sayısı | Kuyruk | Kullanım |
|---|---|---|---|
newFixedThreadPool(n) | Sabit n | Sınırsız | Genel amaçlı |
newCachedThreadPool() | 0 → ∞ | Yok (SynchronousQueue) | Kısa, çok iş |
newSingleThreadExecutor() | 1 | Sınırsız | Sıralı işler |
newScheduledThreadPool(n) | Sabit n | Gecikmeli kuyruk | Zamanlı işler |
submit() vs execute()
ExecutorService pool = Executors.newFixedThreadPool(4);
// execute — void, exception fırlatılırsa yutulur
pool.execute(() -> System.out.println("execute"));
// submit — Future döner, exception yakalanabilir
Future<?> future = pool.submit(() -> System.out.println("submit"));Genel kural: `submit()` kullan. Hata yönetimi ve sonuç takibi daha kolay.
Future — Sonuç Bekleme
submit() bir Future nesnesi döner. Bu nesne ile:
Sonucu al (
get())İşin bitip bitmediğini kontrol et (
isDone())İşi iptal et (
cancel())
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> future = pool.submit(() -> {
Thread.sleep(2000); // 2 saniye süren iş
return 42;
});
System.out.println("İş devam ediyor...");
System.out.println("Bitti mi? " + future.isDone()); // false
// get() — sonuç gelene kadar bekler (blocking)
Integer sonuc = future.get();
System.out.println("Sonuç: " + sonuc); // 42
System.out.println("Bitti mi? " + future.isDone()); // true
pool.shutdown();Zaman Aşımlı get()
try {
Integer sonuc = future.get(3, TimeUnit.SECONDS); // Max 3 saniye bekle
} catch (TimeoutException e) {
System.out.println("Zaman aşımı! İş çok uzun sürüyor.");
future.cancel(true); // İşi iptal et
}İş İptal Etme
Future<?> future = pool.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
// Uzun süren iş
}
});
// İptal et
boolean iptalEdildi = future.cancel(true); // true = interrupt gönder
System.out.println("İptal: " + iptalEdildi);
System.out.println("İptal edildi mi: " + future.isCancelled());Callable — Sonuç Dönen Task
Runnable'ın run() metodu void döner ve checked exception fırlatamaz. Callable bu kısıtlamaları aşar:
// Runnable — void, exception yok
Runnable r = () -> System.out.println("Merhaba");
// Callable — değer döner, exception fırlatabilir
Callable<Integer> c = () -> {
Thread.sleep(1000);
return 42;
};ExecutorService pool = Executors.newFixedThreadPool(3);
Callable<String> gorev1 = () -> {
Thread.sleep(1000);
return "Görev 1 tamamlandı";
};
Callable<String> gorev2 = () -> {
Thread.sleep(2000);
return "Görev 2 tamamlandı";
};
Callable<String> gorev3 = () -> {
Thread.sleep(1500);
return "Görev 3 tamamlandı";
};
Future<String> f1 = pool.submit(gorev1);
Future<String> f2 = pool.submit(gorev2);
Future<String> f3 = pool.submit(gorev3);
// Sonuçları topla
System.out.println(f1.get()); // ~1 saniye sonra
System.out.println(f3.get()); // ~1.5 saniye sonra (zaten başlamıştı)
System.out.println(f2.get()); // ~2 saniye sonra (zaten başlamıştı)
pool.shutdown();invokeAll — Hepsini Çalıştır, Hepsi Bitene Kadar Bekle
List<Callable<String>> gorevler = List.of(
() -> { Thread.sleep(1000); return "A"; },
() -> { Thread.sleep(2000); return "B"; },
() -> { Thread.sleep(500); return "C"; }
);
// Hepsini başlat, hepsi bitince döner
List<Future<String>> sonuclar = pool.invokeAll(gorevler);
for (Future<String> f : sonuclar) {
System.out.println(f.get());
}
// Tüm çıktı ~2 saniye sonra gelir (en uzun görev kadar bekler)invokeAny — İlk Biteni Al
// İlk biten görevin sonucunu döner, diğerlerini iptal eder
String ilkSonuc = pool.invokeAny(gorevler);
System.out.println("İlk biten: " + ilkSonuc); // C (~0.5 saniye)Shutdown — Pool'u Kapat
Pool'u doğru kapatmak önemli. Yoksa program kapanmaz (user thread'ler çalışmaya devam eder).
ExecutorService pool = Executors.newFixedThreadPool(4);
// İşleri submit et...
// 1. Yeni iş kabul etme, mevcutları bitir
pool.shutdown();
// 2. Bitmesini bekle
boolean bitti = pool.awaitTermination(30, TimeUnit.SECONDS);
if (!bitti) {
// 3. Zorla kapat
List<Runnable> bekleyenler = pool.shutdownNow();
System.out.println(bekleyenler.size() + " iş iptal edildi");
}Shutdown Pattern — Best Practice
public static void guvenliKapat(ExecutorService pool) {
pool.shutdown(); // Yeni iş alma
try {
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // 60 saniye dolduysa zorla kapat
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("Pool kapanmadı!");
}
}
} catch (InterruptedException e) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}⚠️ `shutdown()` çağırmazsan program kapanmaz! Pool'daki thread'ler user thread olduğundan JVM bekler. Her zaman
shutdown()çağır — ya da try-with-resources kullan (Java 19+).
Gerçek Hayat Örneği — Paralel Web Scraper
public class ParalelScraper {
static String sayfaIndir(String url) throws Exception {
Thread.sleep(1000); // HTTP isteği simülasyonu
return "İçerik: " + url + " (" + url.length() + " byte)";
}
public static void main(String[] args) throws Exception {
List<String> urls = List.of(
"https://example.com/sayfa1",
"https://example.com/sayfa2",
"https://example.com/sayfa3",
"https://example.com/sayfa4",
"https://example.com/sayfa5"
);
ExecutorService pool = Executors.newFixedThreadPool(3);
long baslangic = System.currentTimeMillis();
// Her URL için Callable oluştur
List<Callable<String>> gorevler = urls.stream()
.map(url -> (Callable<String>) () -> sayfaIndir(url))
.toList();
// Hepsini çalıştır
List<Future<String>> sonuclar = pool.invokeAll(gorevler);
// Sonuçları yazdır
for (Future<String> f : sonuclar) {
System.out.println(f.get());
}
long sure = System.currentTimeMillis() - baslangic;
System.out.println("Toplam süre: " + sure + "ms"); // ~2 saniye (5 iş / 3 thread)
pool.shutdown();
}
}3 thread ile 5 sayfa: İlk 3 sayfa paralel (1 sn), sonra kalan 2 sayfa paralel (1 sn) = ~2 saniye. Sıralı olsa 5 saniye olurdu.
Thread Pool Boyutu Nasıl Belirlenir?
| İş Tipi | Önerilen Pool Boyutu | Neden |
|---|---|---|
| CPU-yoğun (hesaplama) | CPU çekirdek sayısı | Daha fazla thread = context switch maliyeti |
| I/O-yoğun (dosya, ağ, DB) | CPU × 2 veya daha fazla | Thread'ler beklerken CPU boşta |
int cpuSayisi = Runtime.getRuntime().availableProcessors();
System.out.println("CPU: " + cpuSayisi);
// CPU-yoğun iş
ExecutorService cpuPool = Executors.newFixedThreadPool(cpuSayisi);
// I/O-yoğun iş
ExecutorService ioPool = Executors.newFixedThreadPool(cpuSayisi * 2);💡 Genel kural: CPU-yoğun iş için
Nthread, I/O-yoğun iş için2Nveya daha fazla thread kullan (N= CPU çekirdek sayısı). Ama en doğru cevap: ölç ve ayarla (benchmark).
Hata Yönetimi
Future ile hata yakalama:
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> future = pool.submit(() -> {
if (true) throw new RuntimeException("Bir şeyler ters gitti!");
return 42;
});
try {
Integer sonuc = future.get();
} catch (ExecutionException e) {
// Görev içindeki exception burada yakalanır
System.out.println("Görev hatası: " + e.getCause().getMessage());
// Çıktı: Görev hatası: Bir şeyler ters gitti!
}
pool.shutdown();execute() ile submit edilen görevlerde exception sessizce yutulur. submit() + Future.get() ile exception yakalanabilir.
Özet
Thread pool, thread'leri yeniden kullanarak maliyet ve karmaşıklığı azaltır
`Executors.newFixedThreadPool(n)` en yaygın kullanılan pool — sabit n thread
`submit()` ile görev gönder, `Future` ile sonuç al veya iptal et
`Callable` değer döner ve exception fırlatabilir —
Runnable'dan daha güçlü`shutdown()` her zaman çağır — yoksa program kapanmaz
Pool boyutu: CPU-yoğun iş → CPU sayısı, I/O-yoğun → CPU × 2
AI Asistan
Sorularını yanıtlamaya hazır