### Java'da Çoklu İşlem (Multithreading) ve Senkronizasyon
Java'da çoklu işlem (multithreading), aynı anda birden fazla işlem yapabilmek için kullanılır. Bu, uygulamanın performansını artırabilir ve kullanıcı deneyimini iyileştirebilir. Ancak, çoklu işlem ile birlikte senkronizasyon ve senkronizasyon problemleri gibi bazı zorluklar da gelir. Bu ders notları, thread tanımlama ve yönetimi, senkronizasyon, ve `java.util.concurrent` API'si kullanımını kapsamaktadır.
### Thread Tanımlama ve Yönetme
Java'da bir thread (iş parçacığı) tanımlamak ve yönetmek için iki ana yöntem kullanılır:
1. **`Thread` Sınıfını Kullanarak:**
`Thread` sınıfını genişleterek bir thread oluşturabilirsiniz.
```java
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread çalışıyor: " + Thread.currentThread().getId());
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
```
- **`run()` Metodu:** Thread çalıştırıldığında çağrılır.
- **`start()` Metodu:** Thread başlatır ve `run()` metodunu çağırır.
2. **`Runnable` Arayüzünü Kullanarak:**
`Runnable` arayüzünü implement ederek thread oluşturabilirsiniz.
```java
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread çalışıyor: " + Thread.currentThread().getId());
}
}
public class RunnableExample {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
Thread t2 = new Thread(new MyRunnable());
t1.start();
t2.start();
}
}
```
- **`Runnable` Arayüzü:** `run()` metodunu tanımlar.
- **`Thread` Sınıfı:** `Runnable` arayüzünü alarak bir thread başlatır.
### Senkronizasyon ve Senkronizasyon Problemleri
Senkronizasyon, çoklu thread'lerin ortak kaynaklara erişimini düzenlemek için kullanılır. Senkronizasyon problemleri, aynı anda birden fazla thread'in aynı kaynağa erişmeye çalıştığı durumlarda ortaya çıkar.
#### Temel Senkronizasyon
Senkronizasyon, `synchronized` anahtar kelimesi ile sağlanır. Bu, belirli bir kod bloğunun aynı anda sadece bir thread tarafından çalıştırılmasını garanti eder.
```java
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class SyncExample {
public static void main(String[] args) {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + counter.getCount());
}
}
```
- **`synchronized` Anahtar Kelimesi:** Erişim kontrolü sağlar, yalnızca bir thread'in belirli bir kod bloğunu çalıştırmasını sağlar.
- **`join()` Metodu:** Bir thread'in bitmesini bekler.
#### Senkronizasyon Problemleri
1. **Race Condition (Yarış Durumu):** Aynı kaynağa aynı anda erişim sağlandığında ortaya çıkar. Senkronizasyon eksikliği nedeniyle verinin bozulmasına neden olabilir.
2. **Deadlock (Ölü Kilit):** İki veya daha fazla thread'in birbirlerini beklemesi sonucu hiç birinin ilerleyememesi durumu.
3. **Livelock:** Thread'ler birbirlerini sürekli olarak engelleyerek ilerleyemezler, fakat durmazlar.
### Concurrent API Kullanımı
Java 5 ile birlikte, `java.util.concurrent` paketi, daha yüksek seviyeli thread yönetimi ve senkronizasyon araçları sunar.
#### ExecutorService Kullanımı
`ExecutorService`, thread havuzları oluşturmak için kullanılır.
```java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorServiceExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
Runnable task = () -> {
System.out.println("Thread çalışıyor: " + Thread.currentThread().getId());
};
executor.execute(task);
executor.execute(task);
executor.shutdown(); // Thread havuzunu kapatır
}
}
```
- **`Executors.newFixedThreadPool(n)`:** Belirli sayıda thread ile bir thread havuzu oluşturur.
- **`execute(Runnable)`:** Bir görevi havuza ekler ve çalıştırır.
- **`shutdown()`:** Thread havuzunu kapatır ve yeni görevleri kabul etmez.
#### Future ve Callable Kullanımı
`Callable` arayüzü, bir değer döndüren ve hata fırlatabilen görevler oluşturur. `Future`, bu görevlerin sonucunu temsil eder.
```java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(1);
Callable<Integer> callableTask = () -> {
Thread.sleep(1000);
return 123;
};
Future<Integer> future = executor.submit(callableTask);
try {
Integer result = future.get(); // Sonucu alır
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
}
}
```
- **`Callable` Arayüzü:** Sonuç döndüren görevler oluşturur.
- **`submit(Callable)` Metodu:** Callable görevini havuza ekler ve bir `Future` döner.
- **`get()`:** Görevin sonucunu alır.
### Özet
Bu ders notları, Java'da thread tanımlama ve yönetme, senkronizasyon ve senkronizasyon problemleri, ve `java.util.concurrent` API'si kullanımını kapsamaktadır. Çoklu işlem ve senkronizasyon, performansı artırmak ve veri tutarlılığını sağlamak için önemlidir. `java.util.concurrent` API'si, yüksek seviyeli thread yönetimi ve senkronizasyon araçları sağlar, böylece çoklu iş parçacığı yönetimini daha kolay ve güvenli bir şekilde gerçekleştirmenizi sağlar.