Java Thread ve Concurrency Detaylı Rehberi
1. Thread'ler
Thread'ler, bir programın aynı anda birden fazla işi yapabilmesini sağlayan hafif alt işlemlerdir. Java'da her uygulama en az bir thread (main thread) ile başlar.
Thread Oluşturma:
Thread
sınıfını extend etmek:
javapublic class MyThread extends Thread { public void run() { System.out.println("Thread çalışıyor!"); } } MyThread thread = new MyThread(); thread.start();
Runnable
interface'ini implement etmek:
javapublic class MyRunnable implements Runnable { public void run() { System.out.println("Runnable çalışıyor!"); } } Thread thread = new Thread(new MyRunnable()); thread.start();
Thread Yaşam Döngüsü:
- New: Thread oluşturuldu ama henüz başlatılmadı.
- Runnable: Thread çalışmaya hazır veya çalışıyor.
- Blocked: Thread geçici olarak çalışamıyor (örn. bir kaynağın kilidini bekliyor).
- Waiting: Thread başka bir thread'in bir işlemi tamamlamasını bekliyor.
- Timed Waiting: Thread belirli bir süre bekliyor.
- Terminated: Thread çalışmasını tamamladı.
Önemli Thread Metodları:
start()
: Thread'i başlatır.run()
: Thread'in yapacağı işi tanımlar.sleep(long millis)
: Thread'i belirli bir süre bekletir.join()
: Bir thread'in bitmesini bekler.interrupt()
: Thread'i kesintiye uğratır.
2. Synchronization
Synchronization, birden fazla thread'in paylaşılan kaynaklara erişimini kontrol etmek için kullanılır. Bu, veri tutarsızlığını ve race condition'ları önler.
synchronized Keyword:
javapublic class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } }
synchronized Blokları:
javapublic void someMethod() { synchronized(this) { // Kritik bölge } }
Açıklama:
synchronized
metotlar veya bloklar, aynı anda sadece bir thread tarafından yürütülebilir.- Bir thread
synchronized
bloğa girdiğinde, nesnenin kilidini alır ve bloktan çıkana kadar tutar. - Bu, diğer thread'lerin aynı nesne üzerindeki
synchronized
metotlara veya bloklara erişimini engeller.
3. Thread Güvenliği
Thread güvenliği, çoklu thread ortamında veri bütünlüğünü korumak için kritik öneme sahiptir.
Race Condition:
Birden fazla thread'in aynı anda paylaşılan bir kaynağa erişmeye ve değiştirmeye çalışması durumudur.
Örnek:
javapublic class UnsafeCounter { private int count = 0; public void increment() { count++; // Bu işlem atomik değil! } }
Çözüm: synchronized
keyword'ü veya AtomicInteger
kullanmak.
Deadlock:
İki veya daha fazla thread'in birbirlerinin kilitledikleri kaynakları beklemesi durumudur.
Örnek:
javapublic class DeadlockExample { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void method1() { synchronized(lock1) { synchronized(lock2) { // İşlem } } } public void method2() { synchronized(lock2) { synchronized(lock1) { // İşlem } } } }
Çözüm: Kilitleri her zaman aynı sırada almak veya tryLock()
metodunu kullanmak.
Livelock:
Thread'lerin birbirlerinin işlemlerini engellediği, ancak hiçbirinin ilerleyemediği durumdur.
Çözüm: Rastgele bekleme süreleri eklemek veya retry mekanizması kullanmak.
4. Executor Framework
Executor Framework, thread yönetimini kolaylaştırır ve thread havuzları oluşturmayı sağlar.
ExecutorService:
javaExecutorService executor = Executors.newFixedThreadPool(5); executor.submit(() -> { System.out.println("Executor ile çalışan görev"); }); executor.shutdown();
Callable ve Future:
javaCallable<Integer> task = () -> { TimeUnit.SECONDS.sleep(1); return 123; }; Future<Integer> future = executor.submit(task); System.out.println(future.get()); // Blocking call
Açıklama:
ExecutorService
, görevleri yönetir ve thread havuzunu kontrol eder.Callable
, bir değer döndüren ve exception fırlatabilen görevler için kullanılır.Future
, asenkron hesaplamaların sonucunu temsil eder.
5. Concurrent Collections
Java, thread-safe koleksiyonlar sunar:
ConcurrentHashMap:
javaConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("key", 1); map.computeIfAbsent("key2", k -> k.length());
CopyOnWriteArrayList:
javaCopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("item"); for (String item : list) { // Güvenli iterasyon System.out.println(item); }
BlockingQueue:
javaBlockingQueue<String> queue = new LinkedBlockingQueue<>(); queue.put("item"); String item = queue.take(); // Bloke olabilir
Açıklama:
ConcurrentHashMap
: Birden çok thread'in aynı anda okuma yapmasına izin verir, yazma işlemlerini senkronize eder.CopyOnWriteArrayList
: Her yazma işleminde arrayin bir kopyasını oluşturur, okuma işlemleri için idealdir.BlockingQueue
: Üretici-tüketici senaryoları için idealdir, queue boş veya dolu olduğunda thread'leri bloke eder.
6. Lock ve Condition'lar
Lock
interface'i, synchronized
'dan daha esnek bir kilit mekanizması sağlar.
ReentrantLock:
javaLock lock = new ReentrantLock(); lock.lock(); try { // Kritik bölge } finally { lock.unlock(); }
ReadWriteLock:
javaReadWriteLock rwLock = new ReentrantReadWriteLock(); Lock readLock = rwLock.readLock(); Lock writeLock = rwLock.writeLock(); // Okuma işlemi readLock.lock(); try { // Okuma işlemleri } finally { readLock.unlock(); } // Yazma işlemi writeLock.lock(); try { // Yazma işlemleri } finally { writeLock.unlock(); }
Condition:
javaLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try { while (!condition) { condition.await(); } // İşlem yap } finally { lock.unlock(); } // Başka bir thread'de lock.lock(); try { condition.signal(); } finally { lock.unlock(); }
Açıklama:
ReentrantLock
: Aynı thread'in kilidi birden fazla kez almasına izin verir.ReadWriteLock
: Okuma işlemleri için paylaşımlı, yazma işlemleri için özel kilit sağlar.Condition
: Thread'lerin belirli koşulları beklemesini ve sinyalleşmesini sağlar.
7. Atomic Değişkenler
Atomic değişkenler, thread-safe ve lock-free operasyonlar sağlar.
AtomicInteger:
javaAtomicInteger counter = new AtomicInteger(0); int updatedValue = counter.incrementAndGet(); boolean wasSet = counter.compareAndSet(updatedValue, 10);
AtomicReference:
javaAtomicReference<String> ref = new AtomicReference<>("initial"); ref.set("new value"); String oldValue = ref.getAndSet("newer value");
LongAdder:
javaLongAdder adder = new LongAdder(); adder.add(10); adder.increment(); long sum = adder.sum();
Açıklama:
- Atomic değişkenler, çoklu thread ortamında race condition'ları önler.
incrementAndGet()
,compareAndSet()
gibi metodlar atomik olarak çalışır.LongAdder
, yüksek eşzamanlılık durumlarındaAtomicLong
'dan daha iyi performans gösterir.
8. Thread Pools
Thread havuzları, thread oluşturma ve yönetme maliyetini azaltır.
FixedThreadPool:
javaExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { executor.submit(() -> { System.out.println("Task executed by " + Thread.currentThread().getName()); }); } executor.shutdown();
CachedThreadPool:
javaExecutorService executor = Executors.newCachedThreadPool(); // Kullanım FixedThreadPool ile aynı
ScheduledThreadPool:
javaScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); scheduler.scheduleAtFixedRate(() -> { System.out.println("Scheduled task"); }, 0, 1, TimeUnit.SECONDS);
Açıklama:
FixedThreadPool
: Sabit sayıda thread içerir.CachedThreadPool
: İhtiyaca göre yeni thread'ler oluşturur ve kullanılmayan thread'leri sonlandırır.ScheduledThreadPool
: Görevlerin belirli bir gecikme ile veya periyodik olarak çalıştırılmasını sağlar.
9. CompletableFuture
CompletableFuture
, asenkron işlemleri zincirlemek ve birleştirmek için kullanılır.
Asenkron İşlem:
javaCompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // Uzun süren bir işlem return "Result"; }); future.thenAccept(System.out::println);
Zincirleme İşlemler:
javaCompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello") .thenApply(s -> s + " World") .thenApply(String::toUpperCase); System.out.println(future.get()); // HELLO WORLD
Birleştirme:
javaCompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello"); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World"); CompletableFuture<String> combined = future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2); System.out.println(combined.get()); // Hello World
Açıklama:
supplyAsync()
: Asenkron olarak bir değer döndüren işlem başlatır.thenApply()
: Bir önceki aşamanın sonucunu alıp işler ve yeni bir sonuç döndürür.thenAccept()
: Bir önceki aşamanın sonucunu alıp işler, ama yeni bir sonuç döndürmez.thenCombine()
: İki CompletableFuture'ın sonuçlarını birleştirir.
10. Fork/Join Framework
Fork/Join Framework, "böl ve yönet" stratejisini kullanarak büyük görevleri daha küçük alt görevlere böler ve paralel olarak işler.
RecursiveTask:
javapublic class FibonacciTask extends RecursiveTask<Integer> { final int n; FibonacciTask(int n) { this.n = n; } @Override protected Integer compute() { if (n <= 1) return n; FibonacciTask f1 = new FibonacciTask(n - 1); f1.fork(); FibonacciTask f2 = new FibonacciTask(n - 2); return f2.compute() + f1.join(); } } ForkJoinPool pool = new ForkJoinPool(); System.out.println(pool.invoke(new FibonacciTask(10)));
RecursiveAction:
javapublic class SortTask extends RecursiveAction { final int[] array; final int low, high; SortTask(int[] array, int low, int high) { this.array = array; this.low = low; this.high = high; } @Override protected void compute() { if (high - low < THRESHOLD) { Arrays.sort(array, low, high); } else { int mid = (low + high) >>> 1; invokeAll(new SortTask(array, low, mid), new SortTask(array, mid, high)); merge(array, low, mid, high); } } }
Açıklama:
RecursiveTask
: Bir değer döndüren görevler için kullanılır.RecursiveAction
: Bir değer döndürmeyen görevler için kullanılır.fork()
: Alt görevi asenkron olarak başlatır.join()
: Alt görevin sonucunu bekler ve alır.invokeAll()
: Birden fazla alt görevi paralel olarak çalıştırır.
11. Phaser
Phaser
, CountDownLatch
ve CyclicBarrier
'ın özelliklerini birleştiren esnek bir senkronizasyon bariyeridir.
javapublic class PhaserExample { public static void main(String[] args) { Phaser phaser = new Phaser(1); // "1" is for self for (int i = 0; i < 3; i++) { final int threadId = i; phaser.register(); new Thread(() -> { System.out.println("Thread " + threadId + " arriving"); phaser.arriveAndAwaitAdvance(); System.out.println("Thread " + threadId + " after passing barrier"); phaser.arriveAndDeregister(); }).start(); } phaser.arriveAndDeregister(); } }
Açıklama:
register()
: Yeni bir katılımcı ekler.arriveAndAwaitAdvance()
: Fazın tamamlandığını bildirir ve diğer katılımcıları bekler.arriveAndDeregister()
: Fazı tamamlar ve Phaser'dan ayrılır.- Phaser, dinamik sayıda thread'in senkronize edilmesine olanak tanır.
12. Semaphore
Semaphore
, sınırlı sayıda kaynağa erişimi kontrol etmek için kullanılır.
javapublic class SemaphoreExample { private static final int MAX_CONNECTIONS = 3; private static final Semaphore semaphore = new Semaphore(MAX_CONNECTIONS); public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " acquired a connection"); Thread.sleep(2000); // Simulating work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { semaphore.release(); System.out.println(Thread.currentThread().getName() + " released a connection"); } }).start(); } } }
Açıklama:
acquire()
: Bir izin alır, eğer izin yoksa thread bloklanır.release()
: Bir izni serbest bırakır.- Semaphore, örneğin veritabanı bağlantı havuzları gibi sınırlı kaynaklara erişimi kontrol etmek için kullanılabilir.
13. CountDownLatch
CountDownLatch
, bir veya daha fazla thread'in belirli sayıda olayın gerçekleşmesini beklemesini sağlar.
javapublic class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new Thread(() -> { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " completed"); latch.countDown(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); } latch.await(); System.out.println("All threads completed"); } }
Açıklama:
countDown()
: Sayacı bir azaltır.await()
: Sayaç sıfıra ulaşana kadar bekler.- CountDownLatch, bir kez kullanılabilir. Sıfıra ulaştığında tekrar kullanılamaz.
14. CyclicBarrier
CyclicBarrier
, bir grup thread'in belirli bir noktada buluşmasını sağlar.
javapublic class CyclicBarrierExample { public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All threads reached the barrier")); for (int i = 0; i < 3; i++) { new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + " waiting at barrier"); barrier.await(); System.out.println(Thread.currentThread().getName() + " crossed the barrier"); } catch (Exception e) { e.printStackTrace(); } }).start(); } } }
Açıklama:
await()
: Thread'i bariyerde bekletir.- Tüm thread'ler bariyere ulaştığında, isteğe bağlı bir eylem gerçekleştirilebilir.
- CyclicBarrier, adından da anlaşılacağı gibi tekrar kullanılabilir.
15. ThreadLocal
ThreadLocal
, her thread için ayrı bir değer saklamanızı sağlar.
javapublic class ThreadLocalExample { private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0); public static void main(String[] args) { for (int i = 0; i < 2; i++) { new Thread(() -> { threadLocal.set(threadLocal.get() + 1); System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); }).start(); } } }
Açıklama:
- Her thread kendi değerini görür ve değiştirir.
- Thread-local değerler, diğer thread'ler tarafından görülmez.
- Genellikle, her thread için ayrı bir bağlam (context) saklamak için kullanılır.
16. Volatile Keyword
volatile
keyword'ü, bir değişkenin her zaman main memory'den okunup yazılmasını sağlar, bu da thread'ler arası görünürlüğü garanti eder.
javapublic class VolatileExample { private static volatile boolean flag = false; public static void main(String[] args) throws InterruptedException { new Thread(() -> { while (!flag) { // Do nothing, just wait } System.out.println("Flag is true, exiting"); }).start(); Thread.sleep(1000); flag = true; } }
Açıklama:
volatile
değişkenler, her okuma/yazma işleminde doğrudan main memory ile etkileşime girer.- Cache tutarsızlığı problemlerini önler.
- Ancak, atomik operasyonları garanti etmez. Örneğin,
i++
gibi işlemler için yeterli değildir.
17. Java Memory Model ve Happens-Before İlişkisi
Java Memory Model (JMM), thread'lerin nasıl etkileşime girdiğini ve memory operasyonlarının nasıl sıralandığını tanımlar.
Happens-Before İlişkisi:
- Program Order Rule: Bir thread içindeki her eylem, programdaki sıralamaya göre önceki eylemlerden sonra gerçekleşir.
- Monitor Lock Rule: Bir monitör kilidinin serbest bırakılması, aynı kilidin sonraki edinilmesinden önce gerçekleşir.
- Volatile Variable Rule: Bir volatile değişkene yazma, aynı değişkenin sonraki okumalarından önce gerçekleşir.
- Thread Start Rule: Bir thread'de
thread.start()
çağrısı, başlatılan thread'deki herhangi bir eylemden önce gerçekleşir. - Thread Termination Rule: Bir thread'deki tüm eylemler, başka bir thread'in bu thread üzerinde
thread.join()
dönüşünden önce gerçekleşir.
Örnek:
javaclass HappensBefore { int a = 0; volatile boolean flag = false; public void writer() { a = 1; // 1 flag = true; // 2 } public void reader() { if (flag) { // 3 int i = a; // 4 } } }
Bu örnekte, eğer reader thread'i flag'i true olarak okursa (3), writer thread'inin a'ya 1 değerini ataması (1) görünür olacaktır. Bu, volatile write'ın (2) sonraki volatile read'den (3) önce gerçekleşmesi ve 1'in 2'den önce, 3'ün de 4'ten önce gerçekleşmesi (program order rule) nedeniyledir.
18. Concurrency Design Patterns
Concurrency design pattern'ları, çok thread'li uygulamalarda yaygın problemleri çözmek için kullanılan tekniklerdir.
1. Producer-Consumer Pattern:
javapublic class ProducerConsumer { private static final int BUFFER_SIZE = 5; private static BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(BUFFER_SIZE); static class Producer implements Runnable { public void run() { try { for (int i = 0; i < 20; i++) { queue.put(i); System.out.println("Produced: " + i); Thread.sleep(100); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } static class Consumer implements Runnable { public void run() { try { while (true) { Integer item = queue.take(); System.out.println("Consumed: " + item); Thread.sleep(300); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public static void main(String[] args) { new Thread(new Producer()).start(); new Thread(new Consumer()).start(); } }
2. Read-Write Lock Pattern:
javapublic class ReadWriteLockExample { private static final ReadWriteLock lock = new ReentrantReadWriteLock(); private static final Lock readLock = lock.readLock(); private static final Lock writeLock = lock.writeLock(); private static int value = 0; public static int read() { readLock.lock(); try { return value; } finally { readLock.unlock(); } } public static void write(int newValue) { writeLock.lock(); try { value = newValue; } finally { writeLock.unlock(); } } }
Açıklama:
- Producer-Consumer Pattern: Üreticiler ve tüketiciler arasında veri akışını yönetir.
- Read-Write Lock Pattern: Okuma işlemlerinin paralel, yazma işlemlerinin özel olmasını sağlar.
Bu ileri düzey konular ve örnekler, Java'da concurrency ve multi-threading konularında daha derin bir anlayış kazanmanıza yardımcı olacaktır. Bu kavramları pratikte uygulayarak ve farklı senaryolarda deneyerek, zamanla bir uzman haline geleceksiniz.