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

Temel Kavramlar

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

İş parçacığı, bir süreç içinde en küçük yürütme birimidir ve diğer iş parçacıklarıyla aynı bellek alanını paylaşır. Java'da Thread sınıfını genişleterek (extend) veya Runnable arayüzünü uygulayarak (implement) iş parçacıkları oluşturulur. Her iş parçacığı, kendi yığın (stack) alanına sahiptir ve bağımsız olarak çalışabilir.

public class MyThread extends Thread {
    public void run() {
        System.out.println("İş parçacığı çalışıyor: " + Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
        

2. Süreç (Process)

Süreç, işletim sistemi tarafından yönetilen ve kendi bellek alanına, dosya tanımlayıcılarına ve iş parçacıklarına sahip bağımsız bir programdır. Java'da süreçler ProcessBuilder veya Runtime.exec() ile oluşturulabilir. İş parçacıklarından farklı olarak, süreçler birbirleriyle bellek paylaşımı yapamaz.

import java.io.IOException;
public class ProcessExample {
    public static void main(String[] args) throws IOException {
        Process process = new ProcessBuilder("notepad.exe").start();
    }
}
        

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

Eşzamanlılık, birden fazla görevin aynı anda ilerlemesidir, ancak bu görevler aynı anda çalışmak zorunda değildir (örneğin, zaman paylaşımı ile). Java'da Thread veya ExecutorService sınıfları ile eşzamanlılık sağlanır.

Thread t1 = new Thread(() -> System.out.println("Görev 1: " + Thread.currentThread().getName()));
Thread t2 = new Thread(() -> System.out.println("Görev 2: " + Thread.currentThread().getName()));
t1.start(); 
t2.start();
        

4. Paralellik (Parallelism)

Paralellik, birden fazla görevin aynı anda birden fazla CPU çekirdeği üzerinde eşzamanlı olarak yürütülmesidir. Java'da ForkJoinPool veya paralel akışlar (parallelStream()) ile gerçekleştirilir.

import java.util.Arrays;
public class ParallelExample {
    public static void main(String[] args) {
        Arrays.asList(1, 2, 3, 4).parallelStream().forEach(System.out::println);
    }
}
        

5. Bağlam Anahtarlama (Context Switching)

Bağlam anahtarlama, CPU'nun bir iş parçacığından diğerine geçiş yapmasıdır ve her geçişte CPU durumunu (yazmaçlar, yığın) kaydetmek/güncellemek ek yük (overhead) yaratır. Java'da bu, işletim sistemi tarafından yönetilir ve optimize edilmesi için iş parçacığı havuzları kullanılabilir.

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

İş parçacığı havuzu, sabit sayıda iş parçacığının yeniden kullanılarak birçok görevin verimli bir şekilde işlenmesini sağlar. Java'da Executors sınıfı ile oluşturulur.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.submit(() -> System.out.println("Görev 1: " + Thread.currentThread().getName()));
        executor.submit(() -> System.out.println("Görev 2: " + Thread.currentThread().getName()));
        executor.shutdown();
    }
}
        

7. Yarış Durumu (Race Condition)

Yarış durumu, birden fazla iş parçacığının paylaşılan veriye güvenli olmayan bir sırayla erişmesiyle ortaya çıkan hatalı durumdur. Veri tutarsızlığına yol açabilir. Java'da senkronizasyon mekanizmalarıyla önlenir.

public class RaceConditionExample {
    private static int counter = 0;
    public static void main(String[] args) {
        Runnable task = () -> { for(int i = 0; i < 1000; i++) counter++; };
        Thread t1 = new Thread(task); Thread t2 = new Thread(task);
        t1.start(); t2.start(); try { t1.join(); t2.join(); } catch(Exception e) {}
        System.out.println("Counter: " + counter); // Tutarsız sonuçlar mümkün
    }
}
        

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

Mutex, bir kaynağa aynı anda yalnızca bir iş parçacığının erişmesini sağlamak için kullanılan kilit mekanizmasıdır. Java'da ReentrantLock veya synchronized ile uygulanır.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MutexExample {
    private static int counter = 0;
    private static Lock lock = new ReentrantLock();
    public static void increment() {
        lock.lock();
        try { counter++; } finally { lock.unlock(); }
    }
    public static void main(String[] args) {
        Runnable task = () -> { for(int i = 0; i < 1000; i++) increment(); };
        Thread t1 = new Thread(task); Thread t2 = new Thread(task);
        t1.start(); t2.start(); try { t1.join(); t2.join(); } catch(Exception e) {}
        System.out.println("Counter: " + counter);
    }
}
        

9. Kilitlenme (Deadlock)

Kilitlenme, iki veya daha fazla iş parçacığının birbirinin kaynaklarını serbest bırakmasını beklemesiyle oluşan durumdur. Java'da dikkatli kaynak yönetimi ile önlenebilir.

public class DeadlockExample {
    public static void main(String[] args) {
        String resource1 = "Resource 1"; String resource2 = "Resource 2";
        Thread t1 = new Thread(() -> {
            synchronized (resource1) { System.out.println("Thread 1: resource1'i aldı");
                try { Thread.sleep(100); } catch(Exception e) {}
                synchronized (resource2) { System.out.println("Thread 1: resource2'yi aldı"); }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (resource2) { System.out.println("Thread 2: resource2'yi aldı");
                try { Thread.sleep(100); } catch(Exception e) {}
                synchronized (resource1) { System.out.println("Thread 2: resource1'i aldı"); }
            }
        });
        t1.start(); 
		t2.start();
    }
}
        

10. Canlı Kilit (Livelock)

Canlı kilit, iş parçacıklarının birbirine yanıt olarak durumlarını sürekli değiştirmesi, ancak ilerleme kaydedilememesidir. Deadlock'tan farklı olarak aktif bir bekleyiş içerir.

public class LivelockExample {
    static volatile boolean flag1 = true, flag2 = true;
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (flag1) { if (flag2) { flag1 = false; System.out.println("T1 geri adım attı"); } }
        });
        Thread t2 = new Thread(() -> {
            while (flag2) { if (flag1) { flag2 = false; System.out.println("T2 geri adım attı"); } }
        });
        t1.start(); 
		t2.start();
    }
}
        

11. Açlık (Starvation)

Açlık, bir iş parçacığının diğerlerinin kaynakları tekelleştirmesi nedeniyle CPU süresine erişememesi durumudur. Java'da öncelik (priority) yönetimi ile azaltılabilir.

public class StarvationExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> { while(true) { System.out.println("T1 çalışıyor"); } });
        Thread t2 = new Thread(() -> { while(true) { System.out.println("T2 bekliyor"); } });
        t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY);
        t1.start(); 
		t2.start();
    }
}
        

12. Semafor (Semaphore)

Semafor, bir kaynağa çoklu erişimi kontrol eden sinyal mekanizmasıdır. Java'da Semaphore sınıfı ile uygulanır.

import java.util.concurrent.Semaphore;
public class SemaphoreExample {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(2);
        Runnable task = () -> {
            try { semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " erişim aldı");
                Thread.sleep(1000); semaphore.release();
            } catch (InterruptedException e) { e.printStackTrace(); }
        };
        new Thread(task).start(); 
		new Thread(task).start();
    }
}
        

13. Gözetleyici (Monitor)

Monitör, karşılıklı dışlama ve koşul değişkeni kombinasyonuyla iş parçacığı koordinasyonu sağlar. Java'da synchronized bloklar ve wait()/notify() ile uygulanır.

public class MonitorExample {
    private final Object lock = new Object();
    private boolean condition = false;
    public void waitForCondition() {
        synchronized (lock) { while (!condition) try { lock.wait(); } catch (InterruptedException e) {}
            System.out.println("Koşul sağlandı");
        }
    }
    public void setCondition() {
        synchronized (lock) { condition = true; lock.notify(); }
    }
}
        

14. Atomik İşlemler (Atomic Operation)

Atomik işlem, tek bir adımda tamamlanan ve kesintiye uğramayan işlemdir. Java'da java.util.concurrent.atomic paketi ile desteklenir.

import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
    public static void main(String[] args) {
        AtomicInteger atomicInt = new AtomicInteger(0);
        atomicInt.incrementAndGet();
        System.out.println("Değer: " + atomicInt.get());
    }
}
        

15. Uçucu Anahtar Kelime (Volatile Keyword)

volatile, değişkenlerin iş parçacıkları arasında görünürlüğünü garanti eder ve önbellek tutarsızlığını önler. Java'da bellek bariyeri oluşturur.

public class VolatileExample {
    private volatile boolean flag = false;
    public void setFlag() { flag = true; }
    public boolean getFlag() { return flag; }
    public static void main(String[] args) throws InterruptedException {
        VolatileExample example = new VolatileExample();
        Thread t = new Thread(() -> { while (!example.getFlag()) {} System.out.println("Flag true oldu"); });
        t.start(); 
		Thread.sleep(1000); 
		example.setFlag();
    }
}
        

16. Bellek Bariyeri (Memory Barrier)

Bellek bariyeri, CPU optimizasyonlarının talimat sırasını yeniden düzenlemesini engeller. Java'da volatile ve synchronized bu etkiyi sağlar.

17. Yanlış Paylaşım (False Sharing)

Yanlış paylaşım, farklı değişkenlerin aynı CPU önbellek satırında bulunması nedeniyle performans kaybı yaşanmasıdır. Java'da @Contended anotasyonu ile önlenebilir.

import java.util.concurrent.atomic.AtomicLong;
@Contended
public class FalseSharingExample {
    public volatile long value = 0L;
    public static void main(String[] args) throws InterruptedException {
        FalseSharingExample[] array = new FalseSharingExample[2];
        array[0] = new FalseSharingExample(); array[1] = new FalseSharingExample();
        Thread t1 = new Thread(() -> { for(int i = 0; i < 1000000; i++) array[0].value++; });
        Thread t2 = new Thread(() -> { for(int i = 0; i < 1000000; i++) array[1].value++; });
        t1.start(); t2.start(); t1.join(); t2.join();
        System.out.println("Değerler: " + array[0].value + ", " + array[1].value);
    }
}
        

18. İş-Parçacığı-Güvenli Kod (Thread-Safe Code)

İş-parçacığı-güvenli kod, çoklu iş parçacığı tarafından güvenli bir şekilde erişilebilen koddur. Java'da senkronizasyon ve immutable nesneler ile sağlanır.

public class ThreadSafeExample {
    private int count = 0;
    public synchronized int incrementAndGet() { return ++count; }
    public static void main(String[] args) throws InterruptedException {
        ThreadSafeExample example = new ThreadSafeExample();
        Runnable task = () -> { for(int i = 0; i < 1000; i++) example.incrementAndGet(); };
        Thread t1 = new Thread(task); Thread t2 = new Thread(task);
        t1.start(); t2.start(); t1.join(); t2.join();
        System.out.println("Sonuç: " + example.incrementAndGet());
    }
}
        

19. Yeniden Girilebilir Fonksiyon (Reentrant Function)

Yeniden girilebilir fonksiyon, önceki yürütme tamamlanmadan kesilip tekrar çağrılabilen fonksiyondur. Java'da synchronized metodlar genellikle yeniden girilebilir fonksiyonlardır.

public class ReentrantExample {
    public synchronized void method1() {
        System.out.println("method1 çağrıldı");
        method2();
    }
    public synchronized void method2() {
        System.out.println("method2 çağrıldı");
    }
    public static void main(String[] args) {
        ReentrantExample example = new ReentrantExample();
        example.method1();
    }
}
        

20. İş-Parçacığı-Yerel Depolama (Thread-local Storage)

Her iş parçacığına kendi değişken kopyasını sağlar, böylece veri paylaşımı önlenir. Java'da ThreadLocal ile uygulanır.

import java.lang.ThreadLocal;
public class ThreadLocalExample {
    private static ThreadLocal threadLocal = ThreadLocal.withInitial(() -> 0);
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            threadLocal.set(1);
            System.out.println("Thread 1: " + threadLocal.get());
        });
        Thread thread2 = new Thread(() -> {
            threadLocal.set(2);
            System.out.println("Thread 2: " + threadLocal.get());
        });
        thread1.start(); thread2.start();
    }
}
        

21. Futures & Promises

Futures, gelecekte kullanılabilir bir değeri temsil eder (asenkron yürütme için). Java'da Future ve CompletableFuture ile kullanılır.

import java.util.concurrent.Future;
import java.util.concurrent.Executors;
public class FutureExample {
    public static void main(String[] args) throws Exception {
        var executor = Executors.newSingleThreadExecutor();
        Future future = executor.submit(() -> 42);
        System.out.println("Sonuç: " + future.get());
        executor.shutdown();
    }
}
        

22. Asenkron/Await

Asenkron, bloke etmeyen işlemleri yönetmek için kullanılır. Java'da CompletableFuture ile asenkron programlama desteklenir.

import java.util.concurrent.CompletableFuture;
public class AsyncExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> "Merhaba")
            .thenAccept(result -> System.out.println("Sonuç: " + result));
    }
}
        

23. Fork-Join Modeli

Görevlerin alt görevlere bölünmesi (fork) ve sonuçların birleştirilmesi (join) ile paralel işlem yapılır. Java'da ForkJoinPool sınıfı ile uygulanır.

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinExample extends RecursiveTask {
    int start, end;
    ForkJoinExample(int start, int end) { this.start = start; this.end = end; }
    protected Integer compute() {
        if (end - start <= 10) {
            int sum = 0; for (int i = start; i <= end; i++) sum += i;
            return sum;
        }
        int mid = (start + end) / 2;
        ForkJoinExample left = new ForkJoinExample(start, mid);
        ForkJoinExample right = new ForkJoinExample(mid + 1, end);
        left.fork(); right.fork();
        return left.join() + right.join();
    }
    public static void main(String[] args) {
        ForkJoinPool pool = ForkJoinPool.commonPool();
        System.out.println(pool.invoke(new ForkJoinExample(1, 100)));
    }
}
        

24. Üretici-Tüketici Modeli (Producer-Consumer Pattern)

Üreticilerin veri üretip tüketicilerin işlediği klasik çoklu iş parçacıklı modeldir. Java'da BlockingQueue ile uygulanır.

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class ProducerConsumer {
    private static BlockingQueue queue = new LinkedBlockingQueue<>(10);
    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            while (true) try { queue.put(1); System.out.println("Üretildi, boyut: " + queue.size()); }
                catch (InterruptedException e) {}
        });
        Thread consumer = new Thread(() -> {
            while (true) try { queue.take(); System.out.println("Tüketildi, boyut: " + queue.size()); }
                catch (InterruptedException e) {}
        });
        producer.start(); consumer.start();
    }
}
        

25. İş Çalma (Work Stealing)

Boşta olan iş parçacıklarının diğerlerinden görev çalarak iş yükünü dengelemesi yöntemidir. Java'da ForkJoinPool bu mekanizmayı otomatik olarak kullanır.

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
public class WorkStealingExample extends RecursiveAction {
    int start, end;
    WorkStealingExample(int start, int end) { this.start = start; this.end = end; }
    protected void compute() {
        if (end - start <= 10) { for (int i = start; i <= end; i++) System.out.println("İşlem: " + i); }
        else {
            int mid = (start + end) / 2;
            WorkStealingExample left = new WorkStealingExample(start, mid);
            WorkStealingExample right = new WorkStealingExample(mid + 1, end);
            left.fork(); right.compute(); left.join();
        }
    }
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        pool.invoke(new WorkStealingExample(1, 100));
    }
}