Java · Concurrency · Parallelism

Çoklu İş Parçacığı ve Paralellik Kavramları

Bu sayfa Java'da thread, process, concurrency, parallelism ve paylaşılan veri üzerinde güvenli çalışma için kullanılan temel senkronizasyon kavramlarını özetler.

Temel Kavramlar

1. İş Parçacığı (Thread)

İş parçacığı, bir süreç içindeki yürütme akışıdır. Aynı süreçteki iş parçacıkları heap gibi ortak belleği paylaşır; her iş parçacığının kendi stack alanı vardır.

Thread thread = new Thread(() ->
    System.out.println(
        Thread.currentThread().getName()
    )
);

thread.start();
Tercih: doğrudan Thread sınıfını genişletmek yerine çoğu durumda Runnable, Callable ve executor yapıları daha esnektir.

2. Süreç (Process)

Süreç, işletim sistemi tarafından ayrı adres alanı ve kaynaklarla yönetilen çalışan program örneğidir. Java'da dış süreç başlatmak için genellikle ProcessBuilder kullanılır.

Process process =
    new ProcessBuilder("java", "-version")
        .inheritIO()
        .start();

3. Eşzamanlılık (Concurrency)

Eşzamanlılık, birden fazla görevin zaman içinde ilerleme kaydetmesidir. Tek çekirdekte zaman paylaşımıyla da concurrency olabilir.

4. Paralellik (Parallelism)

Paralellik, birden fazla görevin aynı fiziksel zaman aralığında farklı işlem birimlerinde yürütülmesidir. Concurrency ile aynı şey değildir.

KavramAna fikirBirden fazla çekirdek şart mı?
ConcurrencyBirden fazla işin ilerlemesiHayır
ParallelismBirden fazla işin gerçekten aynı anda yürütülmesiGenellikle evet

5. Bağlam Anahtarlama (Context Switching)

İşletim sistemi CPU yürütmesini bir thread'den diğerine geçirirken yürütme durumunu kaydedip geri yükler. Çok fazla thread, context-switching maliyetini artırabilir.

6. İş Parçacığı Havuzu (Thread Pool)

Her görev için yeni thread yaratmak yerine sınırlı sayıda worker thread'in tekrar kullanılması kaynak tüketimini kontrol altında tutar.

ExecutorService executor =
    Executors.newFixedThreadPool(4);

executor.submit(() -> doWork());

executor.shutdown();

Paylaşılan Veri ve Senkronizasyon

7. Yarış Durumu (Race Condition)

Bir programın sonucu, birden fazla thread'in paylaşılan veriye erişim sırasına bağlı hale geliyorsa race condition ortaya çıkabilir.

private static int counter = 0;

// Bu işlem atomik değildir:
counter++;

8. Karşılıklı Dışlama (Mutual Exclusion)

Aynı kritik bölgeye aynı anda yalnızca bir thread'in girmesini sağlar. Java'da synchronized veya Lock kullanılabilir.

private final Lock lock =
    new ReentrantLock();

void increment() {
    lock.lock();
    try {
        counter++;
    } finally {
        lock.unlock();
    }
}

9. Kilitlenme (Deadlock)

İki veya daha fazla thread, birbirinin tuttuğu kaynakları sonsuza kadar beklediğinde deadlock oluşur. En temel önleme yaklaşımı tüm kodda tutarlı lock sıralaması kullanmaktır.

10. Canlı Kilit (Livelock)

Thread'ler bloklanmış değildir; sürekli durum değiştirirler ancak sistem ilerleme kaydedemez.

11. Açlık (Starvation)

Bir thread, diğer thread'lerin kaynakları uzun süre elinde tutması veya adaletsiz scheduling/locking nedeniyle ihtiyaç duyduğu kaynağa sürekli erişemiyorsa starvation oluşur.

Thread priority değerleri starvation için güvenilir bir çözüm değildir; adil kilitler, kısa kritik bölgeler ve doğru executor tasarımı daha önemlidir.

12. Semafor (Semaphore)

Semaphore, aynı anda kaç thread'in belirli bir kaynağa erişebileceğini permit sayısıyla sınırlar.

Semaphore semaphore =
    new Semaphore(2);

semaphore.acquire();
try {
    useLimitedResource();
} finally {
    semaphore.release();
}

13. Monitor

Java'da her nesnenin intrinsic monitor kilidi vardır. synchronized, wait, notify ve notifyAll monitor tabanlı koordinasyon sağlar.

synchronized (lock) {
    while (!condition) {
        lock.wait();
    }
}

Görünürlük ve Atomiklik

14. Atomik İşlemler

AtomicInteger, AtomicLong gibi sınıflar belirli read-modify-write işlemlerini lock kullanmadan atomik biçimde gerçekleştirebilir.

AtomicInteger counter =
    new AtomicInteger();

counter.incrementAndGet();

15. volatile

volatile, bir değişken üzerindeki yazmaların diğer thread'ler tarafından görünürlüğünü ve belirli happens-before ilişkilerini sağlar. Ancak bileşik işlemleri otomatik olarak atomik yapmaz.

private volatile boolean running = true;
Önemli: volatile int counter kullanmak counter++ işlemini thread-safe yapmaz.

16. Bellek Bariyerleri ve Java Memory Model

Programcıların doğrudan CPU memory barrier yönetmesi yerine Java Memory Model içindeki synchronized, volatile, thread start/join ve concurrent collections gibi yapılar üzerinden happens-before garantilerini düşünmesi gerekir.

17. False Sharing

Farklı thread'lerin bağımsız değişkenleri aynı CPU cache line üzerinde yoğun biçimde güncellemesi performans kaybına yol açabilir. Bu durum correctness hatası değil, performans problemidir.

Modern Java Concurrency Araçları

  • ExecutorService — görev çalıştırma ve thread pool yönetimi
  • Callable / Future — sonuç döndüren görevler
  • CompletableFuture — asenkron işlem zincirleri
  • ConcurrentHashMap — concurrent map erişimi
  • CopyOnWriteArrayList — read-heavy senaryolar
  • BlockingQueue — producer-consumer yapıları
  • CountDownLatch, CyclicBarrier, Phaser — koordinasyon
  • ForkJoinPool — divide-and-conquer paralelliği

Parallel Stream Hakkında

long count = values.parallelStream()
    .filter(this::expensiveCheck)
    .count();

Parallel stream her zaman daha hızlı değildir. Küçük veri kümeleri, blocking I/O, shared mutable state ve yüksek koordinasyon maliyeti performansı düşürebilir.

Common Mistakes

  • start() yerine yanlışlıkla doğrudan run() çağırmak.
  • volatile ile atomikliği karıştırmak.
  • Lock sırasını farklı kod yollarında değiştirmek.
  • InterruptedException yakalayıp yok saymak.
  • Executor'ı kapatmayı unutmak.
  • Sınırsız sayıda thread yaratmak.
  • Thread priority değerlerine correctness mekanizması gibi güvenmek.

Özet

Java concurrency'nin temelini üç soru oluşturur: işler nasıl schedule edilir, paylaşılan veri nasıl korunur ve bir thread'in yaptığı değişiklik diğer thread'ler tarafından ne zaman görülür? Thread pool'lar scheduling'i, lock ve atomic yapılar correctness'i, Java Memory Model ise görünürlük ve sıralamayı anlamaya yardımcı olur.