Ç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();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.
| Kavram | Ana fikir | Birden fazla çekirdek şart mı? |
|---|---|---|
| Concurrency | Birden fazla işin ilerlemesi | Hayır |
| Parallelism | Birden fazla işin gerçekten aynı anda yürütülmesi | Genellikle 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;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önetimiCallable/Future— sonuç döndüren görevlerCompletableFuture— asenkron işlem zincirleriConcurrentHashMap— concurrent map erişimiCopyOnWriteArrayList— read-heavy senaryolarBlockingQueue— producer-consumer yapılarıCountDownLatch,CyclicBarrier,Phaser— koordinasyonForkJoinPool— 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ğrudanrun()çağırmak.volatileile atomikliği karıştırmak.- Lock sırasını farklı kod yollarında değiştirmek.
InterruptedExceptionyakalayı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.