← Back to list

Java Thread’ler ve Modern Concurrency: CPU-bound, Thread Pool, BlockingQueue, I/O-bound İşler ve…

Modern uygulamalarda performans, verimlilik ve kullanıcı deneyimi; büyük ölçüde eşzamanlı işlemleri nasıl yönettiğimize bağlıdır. Java…

Didem AĞDOĞAN in Softtech · 2025-12-02 18:59 · 4 claps · 10.3 min read
#java #threads #concurrency #queue #blockingqueue
Open on Medium ↗

Java Thread’ler ve Modern Concurrency: CPU-bound, Thread Pool, BlockingQueue, I/O-bound İşler ve Lock-Free Tasarım

Modern uygulamalarda performans, verimlilik ve kullanıcı deneyimi; büyük ölçüde eşzamanlı işlemleri nasıl yönettiğimize bağlıdır. Java ekosisteminde bu ihtiyacın temelini Thread yapıları oluşturur.

Generated by AI

Generated by AI

Thread (iş parçacığı); bir uygulama içinde bağımsız olarak çalışabilen en küçük yürütme birimidir. Tipik bir Java uygulaması main thread üzerinde başlar. Ancak aynı anda farklı işler yapmak istediğimizde yeni thread’ler oluştururuz. Böylece görevler sanki paralel çalışıyormuş gibi görünür.

Burada kritik bir noktayı netleştirelim:

  • Eğer sistemde yalnızca tek bir CPU varsa thread’ler gerçekten paralel çalışmaz.
  • CPU, thread’ler arasında context switch yaparak her birine çok kısa süreler ayırır. Biz de bu hızlı geçişi paralellik gibi algılarız.

Peki çok çekirdekli sistemlerde durum nasıl?

Çok çekirdekli (multi-core) CPU’larda teorik olarak aynı anda en fazla çekirdek sayısı kadar thread gerçekten paralel çalışabilir. Yani 4 çekirdekli bir sunucuda:

  • Aynı anda maksimum 4 thread gerçekten çalışır.
  • Geri kalan thread’ler sırada bekler.
  • CPU periyodik olarak context switch yaparak sıradaki thread’i çalıştırır.

Kurumsal bir Spring Boot uygulamasında thread sayısı genellikle çekirdek sayısından çok daha fazladır. Örneğin:

  • Tomcat worker thread pool → ~200 thread
  • GC thread’leri → 4–10
  • Spring @Async thread pool
  • Logging / Monitoring thread’leri
  • Kafka consumer thread’leri (partition sayısına göre)
  • Database driver thread’leri
  • OS thread’leri

Toplamda 100–1000+ thread olması tamamen normaldir.

Bu yüzden thread sayısı kontrol edilmezse context switch maliyeti artar, CPU sürekli thread değiştirirken gerçek iş yapamaz hale gelir.

Native Thread Nedir?

Native thread, işletim sisteminin yönettiği gerçek thread’dir. Java Thread sınıfı kullanıldığında, JVM bu thread’i işletim sisteminde bir native thread olarak başlatır.

ThreadPoolExecutor kullanırken maximumPoolSize’i çok yüksek ayarlarsan, native thread limitini aşabilirsin. “OutOfMemoryError: unable to create new native thread.” hatası verir.

JVM’de her thread için bir miktar bellek (stack) ayırılır. Bu da sistem kaynaklarını tüketir.

-XX:ThreadStackSize=512 // Her thread için 512 KB stack alanı ayır demektir.

Bu stack ne işe yarar?

Java’da her thread kendi call stack’ine sahiptir. Method çağrıları, yerel değişkenler (primitive’ler ve referanslar), parametreler, return adresleri thread stack’inde tutulur. Derin recursive işlemler veya çok fazla local değişken tanımı olan metodlar bu stack’i daha çabuk doldurur.

  • Stack boyutu ne kadar küçük olursa: Daha fazla thread oluşturabilirsin, çünkü JVM toplam belleği daha verimli kullanır. Ama çok küçük stack, StackOverflowError riskini artırır.
  • Stack boyutu ne kadar büyük olursa: Daha az thread oluşturabilirsin, çünkü her biri daha fazla bellek yer. Büyük recursive çağrılar desteklenir.

Java’da bir thread yaşam döngüsü boyunca aşağıdaki durumlardan geçer:

  • New → Oluşturuldu ancak henüz çalışmaya başlamadı (start() çağrılmadı).
  • Runnable → Çalışmaya hazır ve CPU tarafından zamanlanmayı bekliyor.
  • Running → Thread aktif olarak CPU üzerinde çalışıyor.
  • Waiting / Blocked → Geçici olarak bekliyor fakat hâlâ canlı.
  • Terminated (Dead) → Görev tamamlandı ve thread yaşam döngüsü bitti.

Thread yönetimi için temel kavramlar arasında CPU-bound vs I/O-bound işler var.

CPU-bound (CPU’ya bağımlı işler): Bu tür işlemler sürekli işlemci gücü tüketir. Yani CPU sürekli hesap yapar, bekleme yoktur. Örneğin; matematiksel hesaplamalar (image processing, ML inferencing), kriptografi (hash, encryption), sorting, encoding, compression, Video rendering gibi. Bu işler CPU yoğun olduğu için; fazla thread eklemek performansı düşürür (çok context switch = overhead)

I/O-bound (I/O’ya bağımlı işler): Bu işlemler CPU’yu değil dış kaynakları bekler. Örneğin; DB çağrısı, kafka/SQS mesaj bekleme, network request, file read/write, S3 upload/download gibi. Thread’lerin büyük kısmı bekleme durumunda olduğu için az thread olursa CPU boşta kalır ve sistem kapasitesi kullanılamaz. Bu yüzden CPU çekirdeğinden daha fazla thread kullanarak bazı thread’ler I/O beklerken CPU’nun diğer thread’leri çalıştırması sağlanır.

Java’da Thread Tanımlamak

JVM çalıştığında varsayılan olarak main thread ile başlar. Ancak biz uygulama içinde istediğimiz kadar yeni thread oluşturabiliriz. En basit şekli ile şu şekilde tanımlarız.

“new Thread()” boş haliyle başlatınca aslında hiçbir iş yapmaz. Ona bir task vermek için constructor’a Runnable geçmek gerekiyor.

Runnable task = () -> {
    System.out.println("Hello from: " + Thread.currentThread().getName());
};

Thread th = new Thread(task);  // Runnable veriyoruz
th.start();                    // JVM yeni bir thread açar ve çalıştırır

new Thread() tek başına hiçbir iş yapmaz. Thread’e çalışacağı işi (task) Runnable ile vermemiz gerekir.

Java’da eşzamanlı iş yazmanın iki klasik yolu vardır:

extends Thread: Thread’i özelleştirmek istiyorsak

implements Runnable: İş mantığını tanımlamak için tercih edilen modern yaklaşım

Thread sınıfını genişletmek

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Work in: " + Thread.currentThread().getName());
    }
}
Thread th = new MyThread();
th.start();

Ne zaman tercih edilir?

Bir kütüphane/altyapı yazıyor ve “özel bir thread türü”ne bilinçli olarak ihtiyaç duyuyorsanız. Thread’in kendisinde ek alanlar/API ve yaşam döngüsü override’ları gerekiyorsa anlamlıdır.

Runnable implement etmek

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Work in: " + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        Thread t2 = new Thread(new MyRunnable());
        t2.start();
    }
}

Bu yaklaşımın güzelliği: İşi tanımlayan sınıf başka bir sınıfı extend etmeye devam edebilir. (Single inheritance kısıtına takılmayız)

Runnable ve Thread Farkı nedir?

extends Thread:

  • Daha basit görünüyor ama Java’da sadece bir sınıf extend edilebildiği için dezavantajı var diyebiliriz. (single inheritance).
  • Yani MyThread başka bir sınıfı extend edemez.
  • Thread class kullanımında; her thread unique object yaratır.
  • Thread class bir çok metot kullanımı sunuyor. Örneğin; getPriority(), isAlive.

implements Runnable:

  • Daha esnek çünkü Runnable sadece bir interface.
  • Kendi sınıfın başka bir sınıfı extend edebilir, aynı zamanda Runnable implement edebilir.
  • Production kodlarda Runnable tercih edilir.
  • Runnable interface; farklı threadler aynı objeyi paylaşır.
  • Runnable interface sadece tek method bize sunuyor. run()

Production kodlarda her zaman Runnable/Callable + ExecutorService kullanılması tavsiye edilir.

Thread kullanırken; senaryo gereği thread bekletebilir veya sleep yapabiliriz. Peki bunların farkları nedir?

wait(): Object sınıfta tanımlanır ve method lock serbest bırakır.

sleep(): Thread class da tanımlanır ve lock serbest bırakmaz.

Runnable ve Callable — Asenkron İşlerin Temel Taşları

Runnable ve Callable, Java concurrency’nin temel taşları olsada, Java 8 sonrası, genelde ExecutorService/CompletableFuture kullanılıyor çünkü hem değer döndürme hem de async zincirleme imkanı sağlıyor.

Runnable: Değer döndürmez ve checked exception fırlatamaz. Genelde Thread veya ExecutorService ile kullanılır.

@FunctionalInterface
public interface Runnable {
    void run();
}
Runnable task = () -> {
    System.out.println("Running in thread: " + Thread.currentThread().getName());
};
new Thread(task).start();

Callable: Generic tip ile bir değer döndürebilir. Checked exception fırlatabilir. ExecutorService.submit() ile beraber kullanılır ve Future döner.

Callable<Integer> task = () -> {
    Thread.sleep(1000);
    return 42;
};

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(task);
System.out.println("Result = " + future.get()); // blocking
executor.shutdown();

Ne zaman hangisi?

Runnable: Log yazma, dosya silme, notification gönderme gibi geri dönüş gerektirmeyen işler.

Callable: Hesaplama, servis çağrısı gibi sonuç beklenen işler.

Thread Pool Mekanizmaları

Java’da thread yönetimini elle yapmak yerine havuz mantığı kullanırız. Bu sayede:

  • Thread yeniden kullanılır
  • Kaynak tüketimi kontrol edilir
  • Uygulama daha kararlı çalışır

Java’nın thread pool dünyasını birkaç ana kavrama bölebiliriz:

  • Executor / ExecutorService (arayüz): “görev”i çalıştırma.
  • ThreadPoolExecutor (uygulama): Klasik sabit/ölçeklenebilir iş parçası havuzu (queue + worker threads).
  • ForkJoinPool (uygulama): work-stealing tabanlı, küçük işlere böl-ve-fethet (fork/join) modeli için optimize havuz.
  • ScheduledThreadPoolExecutor: zamanlanmış/periyodik işler.
  • Executors.newVirtualThreadPerTaskExecutor(): her görev için sanal thread.(Java 21)

Executors — Hazır Thread Pool

newFixedThreadPool(n): Sabit sayıda thread. I/O-bound işlerde tercih edilir.

newCachedThreadPool(): İhtiyaç oldukça thread yaratır. Kısa ama çok sayıda iş için ideal.

newSingleThreadExecutor(): Tüm işleri sırayla tek thread’de yapar (FIFO gibi).

Executors.newVirtualThreadPerTaskExecutor() (Java 21+): Her iş için sanal thread (lightweight).

Zamanlanmış İşler

Arka planda periyodik işler için: ScheduledThreadPoolExecutor

scheduleAtFixedRate:  Başlangıç zamanından itibaren sabit aralıklarla  
scheduler.scheduleAtFixedRate(() -> {
    System.out.println("Her 10 sn de çalışır.");
}, 0, 10, TimeUnit.SECONDS);

scheduleWithFixedDelay: Her çalışmadan sonra, belirli süre bekler
scheduler.scheduleWithFixedDelay(() -> {
    System.out.println("Bir önceki bitişten 5 sn sonra çalışır.");
}, 0, 5, TimeUnit.SECONDS);

Executor & ExecutorService — Thread Yönetiminin Omurgası

Java’da artık her yere new Thread(…) yazmak yok 😄 Thread yönetimi Executor ailesiyle soyutlanıyor.

Executor: Executor, Java’da thread yönetimini soyutlayan en temel arabirimdir. Yalnızca tek bir metodu vardır: En Temel Soyutlama

void execute(Runnable command);

Bu, “şu işi bir şekilde çalıştır” demektir. Nasıl çalıştıracağını yeni bir thread’de mi, havuzdan mı Executor’ın implementasyonu belirler.

Amaç: Thread yönetimi kodunu (new Thread(…)) elle yazmak yerine, kontrolü bir yürütücüye (executor) devretmek.

ExecutorService: ExecutorService, Executor’ın gelişmiş sürümüdür. Görevlerin (task) yaşam döngüsünü yönetir ve sonuç döndürebilen API’ler sunar.

Sunduğu yeteneklerden bazıları:

submit(Callable): Task’i çalıştırır ve sonucu Future ile döner

submit(Runnable): Task’i çalıştırır, sonucu olmayan Future döner

invokeAll(List<Callable>): Tüm görevleri paralel çalıştırır, hepsi tamamlanınca sonucu döner

invokeAny(List<Callable>): İlk tamamlanan görevin sonucunu döner, diğerlerini iptal eder

shutdown(): Yeni task kabul etmez, mevcutlar bitince kapanır

shutdownNow(): Tüm görevleri durdurmaya çalışır (Thread.interrupt)

awaitTermination(timeout, unit): shutdown() sonrası tüm görevlerin bitmesini bekler

submit() ve invokeAll() Farkı

//submit() ile
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<String> future = executor.submit(() -> "Hello!");
String result = future.get(); // bloklar
//invokeAll() ile
List<Callable<String>> tasks = List.of(
    () -> "task1",
    () -> "task2"
);
List<Future<String>> results = executor.invokeAll(tasks);
executor.shutdown();
executor.awaitTermination(60, TimeUnit.SECONDS);

Bu kod, executor’ın tüm işler bitene kadar en fazla 60 saniye beklemesini sağlar. Kısacası ExecutorService, thread havuzlarını üretim ortamında kullanılabilir hale getiren API katmanıdır.

ThreadPoolExecutor

Production ortamında gerçek thread havuzunu yöneten sınıf budur.

Ne zaman? I/O-bound veya birbirinden bağımsız küçük/orta işler; web istekleri, e-posta gönderimi, batch adımları, vs.

Nasıl çalışır?

  • Görevler (Runnable / Callable) bir BlockingQueue’ya girer; uygun worker thread alınca çalıştırır.
  • Sıralama, kuyruğun tipine bağlıdır. FIFO genel ama garanti değildir; seçtiğin kuyruğa bağlıdır. (FIFO LinkedBlockingQueue/ArrayBlockingQueue, doğrudan elden ele SynchronousQueue, öncelikli PriorityBlockingQueue vb.).
  • Boyutlandırma: corePoolSize, maximumPoolSize, keepAliveTime ve kuyruk tipi birlikte davranışı belirler.
ThreadPoolExecutor emailSender = new ThreadPoolExecutor(
    3, 10, 60, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(500),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.CallerRunsPolicy() // doluysa işi main thread yapsın
);
emailSender.submit(() -> {
    emailService.send("Welcome!");
});

Kuyruk seçimi davranışı belirler:

ArrayBlockingQueue: Bounded FIFO

LinkedBlockingQueue: Unbounded FIFO

SynchronousQueue: Handoff: queue yok → direkt thread gerek

PriorityBlockingQueue: Önceliklendirme

Thread-Safe Yapılar — Hangi Senaryoda Ne Kullanmalıyız?

Java concurrency’de veri tutarlılığını sağlamak için farklı araçlar vardır. Her biri farklı problem tipleri için optimize edilmiştir.

Lock Mekanizmaları (Synchronized & Locks)

Synchronized: Java’nın en temel thread-safe anahtarıdır. İki seviye lock kullanır.

Static synchronized metot: Lock sınıf seviyesindedir. Yani farklı thread’ler aynı anda bu sınıfın herhangi bir static synchronized metoduna giremez. staticMethod1() ve staticMethod2() aynı anda çalışamaz çünkü ikisi de class-level lock kullanır. Örneğin:

public static synchronized void staticMethod1() {}

public static synchronized void staticMethod2() {}

Instance synchronized metot: Lock nesne seviyesindedir (this). Yani farklı thread’ler aynı nesne örneği üzerinde aynı anda synchronized metot çağırmaya çalışırsa biri beklemek zorunda kalır.

Elimizde bu şekilde bir class var diyelim;

public class MyClass {
    public synchronized void instanceMethod() { }
    public static synchronized void staticMethod1() { }
    public static synchronized void staticMethod2() { }
    public void normalMethod() { }
}

public synchronized void instanceMethod() {} : instanceMethod() aynı anda aynı nesne üzerinde iki farklı thread tarafından çağrılamaz. Ancak farklı nesne örneklerinde aynı anda çağrılabilir.

staticMethod1() çağrılırken instanceMethod() aynı anda çalışabilir, çünkü biri class-level lock (MyClass.class), diğeri object-level lock (this) kullanır. Ama staticMethod1() ve staticMethod2() aynı anda çalışamaz.

Synchronized keyword’unu method ve belirli bir kod bloğu için kullanabiliriz. Fakat tüm bir metotu locklamak çok doğru bir yaklaşım değildir. Bunun yerine ihtiyacımız olan kod parçasını bloklamak daha doğru yaklaşım.

ReentrantLock & ReentrantReadWriteLock

Synchronized’ın daha esnek ve güçlü hali. Java’da concurrency için kullanılan özel lock türü olan ReentrantLock ve ReentrantReadWriteLock daha çok flexible sağladığı için tercih ederiz. ReentrantReadWriteLock okuma ve yazma için ayrı lock imkanı sağlar. Okuma işlemleri çok sık, yazma işlemleri nadir olduğunda.Yani “read-heavy” senaryolarda performans artışı sağlar.

ReentrantLock: Tek bir lock var. Aynı anda yalnızca bir thread lock’u alabilir. ReentrantReadWriteLock: İki ayrı lock sağlar: readLock() → Çoklu thread aynı anda alabilir. writeLock() → Yalnızca bir thread alabilir.

Bir cache düşünelim. Çok sayıda thread sürekli cache’den okuyor, arada bir de güncelleme oluyor.

public class ReadWriteCache {
    private final Map<String, String> cache = new HashMap<>();
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    public String get(String key) {
        rwLock.readLock().lock();  // birden çok thread aynı anda alabilir
        try {
            return cache.get(key);
        } finally {
            rwLock.readLock().unlock();
        }
    }
    public void put(String key, String value) {
        rwLock.writeLock().lock();  // sadece 1 thread alabilir
        try {
            cache.put(key, value);
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

read yaptığında da lock yapılıyor, ama bu paylaşımlı bir lock yapıyor. Sadece yazma ile çakışmayı engelliyor, diğer okuyucuları engellemiyor.

lock(): Sonsuza kadar bekler. Deadlock riski vardır.

tryLock(): Hemen lock alınabiliyorsa true döner, yoksa false.

tryLock(timeout, unit): Belirtilen süre kadar bekler. Süre sonunda hala alamazsa false.

StampedLock

Java 8 ile geldi. Read/Write Lock gibi ama daha hızlı. Ayrıca Optimistic Read desteği var. Okuma sırasında yazma yoksa lock’suz gibi davranabilir. Yüksek performanslı cache / memory access için kullanılır. Bunlar doğrudan Lock arayüzü değil ama benzer şekilde thread koordinasyonu sağlar:

  • Semaphore → Belirli sayıda izin verir. (ör. max 5 thread aynı anda erişsin)
  • CountDownLatch → Bir sayacın sıfıra inmesini bekleyen mekanizma.
  • CyclicBarrier → Belirli sayıda thread baraj noktasına gelene kadar bekler.
  • Phaser → CyclicBarrier’ın daha gelişmiş hali, faz kavramı var.
  • Exchanger → İki thread arasında veri değişimi için lock benzeri mekanizma.

Volatile — Görünürlük Ama Atomic Değil

Değer her zaman main memory’dedir. Tüm thread’ler güncel değeri görür. x++ gibi bileşik işlemler atomik değildir.

“volatile = visibility guarantee” “atomicity garanti etmez”

Atomic Sınıflar — Lock-Free Performans

AtomicXXX sınıfları (AtomicInteger, AtomicBoolean) sadece primitive değerlerle çalışırken, AtomicReference ile herhangi bir nesne (object) üzerinde güvenli işlemler yapabilirsin.

updateAndGet(…) metodu: Yeni nesne yaratır ve atomik olarak günceller.

private final AtomicBoolean hasRun = new AtomicBoolean(false);

  • Bu değer de RAM’de (heap’te) tutulur.
  • CAS (Compare-And-Swap) tabanlı, lock kullanmaz ama thread-safe’dir.
  • Ayrıca volatile davranışı gösterir. Değerin her zaman en güncel hali tüm thread’lere görünür.

AtomicReference<T> Nedir?

Herhangi bir nesne (T) için thread-safe okuma ve güncelleme sağlar. Temel fark: compareAndSet(…) gibi metodlarla lock kullanmadan veri güncellenebilir.

AtomicReference<User> userRef = new AtomicReference<>(new User(“Ali”));

Tek bir değişken için Atomic Class kullanmak synchronized’a göre tercih edilmelidir. Çünkü bu durum daha çok concurrency ve performans sağlar.

synchronized yerine neden AtomicInteger?

AtomicInteger daha hızlıdır çünkü lock-free çalışır.

Fakat sadece ++, compareAndSet gibi basit işlemler için uygundur.

Yani…

Basit senaryolarda: synchronized

Daha esnek kontrol: ReentrantLock

Çok okuma — az yazma: ReentrantReadWriteLock

Çok yüksek performanslı senaryolar: StampedLock

Thread koordinasyonu: Semaphore, CountDownLatch, CyclicBarrier, Phaser

Lock-free senaryolar: Atomics

VirtualThread

Java 21 ile birlikte gelen Virtual Thread kavramı, özellikle I/O-bound uygulamalarda concurrency modelini ciddi anlamda değiştiriyor.

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

Bu yapı:

  • Her submit() çağrısında yeni bir virtual thread oluşturur
  • Ama bu thread, klasik anlamda platform (native) thread değildir
  • JVM içinde, user-mode’da planlanır ve az sayıdaki platform thread üzerinde çalışır

Platform Thread: OS tarafından yönetilir, native thread’dir. Ağırdır, az sayıda olması gerekir.

Virtual Thread: Java tarafından user-mode’da yönetilir. Hafiftir, milyonlarca başlatabilirsin. OS thread tüketmez.

Virtual thread’ler, özellikle blocking I/O ağırlıklı web servislerinde, thread pool boyutu ayarlama derdini büyük ölçüde azaltıyor. Artık ‘kaç thread açsam?’ yerine ‘uygulamam ne kadar I/O yapıyor?’ sorusu daha kritik hale geliyor.

Queue Kullanırken Yapılan Yanlışlar

Producer–Consumer yapılarında en çok yapılan hata: Boş kuyruğu sürekli poll() ederek CPU’yu yakmak.

//poll() ile kötü kullanım:
while (true) {
    Payment p = paymentQueue.poll(); // boşsa null döner
    if (p != null) {
        process(p);
    }
    // CPU burada deli gibi döner — sürekli while içinde çalışıyor
}
//take() ile doğru kullanım:
while (true) {
    Payment p = paymentQueue.take(); // boşsa bekler, CPU tüketmez
    process(p);
}
  • take() kuyruğa yeni bir ödeme gelene kadar thread’i park eder (block).
  • JVM bu thread’i uyutur → OS thread scheduling ile CPU boşta kalır.
  • CPU hiçbir şey yapmaz ta ki queue’ya yeni bir eleman gelene kadar.

Peki take() neyi bekliyor?

Kuyruğa yeni bir eleman gelmesini. paymentQueue.put(new Payment(…)); yazıldığında, take() o an uyandırılır ve gelen elemanı alır.

Kurumsal Tavsiye

  • take(): thread-safe consumer yapılarında tercih edilir (event listener, message consumer).
  • poll(timeout): zaman sınırlı veya son çare beklemeleri için uygundur (örneğin: kullanıcı 30 saniye içinde cevap vermezse işlem iptal).
  • Eğer poll() kullanıyorsan mutlaka: Payment p = queue.poll(2, TimeUnit.SECONDS) gibi bir timeout vererek CPU’yu korumalısın.

BlockingQueue ve ConcurrentLinkedQueue Java’daki çok iş parçacıklı (multi-threaded) ortamlarda kullanılan farklı kuyruk türleridir. BlockingQueue, işlem sırasını ve akış kontrolünü de sağlar.

BlockingQueue: Thread’ler arasında senkron iletişim sağlar. Kuyruk boşsa bekletir, doluysa durdurur.

ConcurrentLinkedQueue: Non-blocking, lock-free, yüksek performanslı asenkron kuyruktur. Bekleme yapmaz.

sleep() Neden Kötüdür?

while (true) {
    String log = logQueue.poll();
    if (log != null) {
        writeToDisk(log);
    }
    // sleep olsa bile CPU döngüyü sürdürür → gereksiz 
}
// Sorunu geciktirir, çözmez.
// CPU yine gereksiz çalışır.
// Üretici ile gerçek eşzamanlılık sağlanmaz.

Hangi Senaryoda Hangisi?

I/O-bound, event-driven workflow varsa -> BlockingQueue + take()

Çok hızlı, drop edilebilir log/telemetry varsa -> ConcurrentLinkedQueue

Timeout gerekli durumlar varsa -> poll(timeout)

Biraz uzun bir yazı oldu. Buraya kadar benimleyseniz 🙂 diğer yazılarda görüşmek üzere…


메타데이터
post_id
87f72d17a770
slug
java-threadler-ve-modern-concurrency-cpu-bound-thread-pool-blockingqueue-i-o-bound-i̇şler-ve-87f72d17a770
url
https://medium.com/softtechas/java-threadler-ve-modern-concurrency-cpu-bound-thread-pool-blockingqueue-i-o-bound-i%CC%87%C5%9Fler-ve-87f72d17a770
canonical_url
https://medium.com/softtechas/java-threadler-ve-modern-concurrency-cpu-bound-thread-pool-blockingqueue-i-o-bound-i%CC%87%C5%9Fler-ve-87f72d17a770
author_url
https://medium.com/@didemagdogan
status
ok
fetched_at
2026-08-02 03:37:55