Ders İçeriği
Java, çoklu iş parçacığı (multithreading) desteği sayesinde aynı anda birden fazla görevi eş zamanlı olarak yürütebilen uygulamalar geliştirmemizi sağlar. Bu, özellikle performans gerektiren uygulamalar, kullanıcı arayüzleri ve sunucu tarafı programlama için kritik öneme sahiptir. Bu bölümde, Java'da iş parçacığı oluşturma, yönetme ve senkronizasyon konularını inceleyeceğiz.
İş Parçacığı (Thread) Nedir?
İş parçacığı, bir programın (prosesin) en küçük yürütme birimidir. Her program en az bir iş parçacığına (ana iş parçacığı) sahiptir. Çoklu iş parçacığı, bir programın aynı anda birden fazla iş parçacığını çalıştırması anlamına gelir. Bu, CPU'nun boşta kalmasını önleyerek uygulamanın daha verimli çalışmasını sağlar.
Java'da İş Parçacığı Oluşturma
Java'da iş parçacığı oluşturmanın iki temel yolu vardır:
1. Thread
Sınıfını Genişleterek
java.lang.Thread
sınıfını genişleterek kendi iş parçacığı sınıfımızı oluşturabiliriz. Bu sınıfın run()
metodunu geçersiz kılmamız (override) gerekir. run()
metodu, iş parçacığının yürüteceği kodu içerir.
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(500); // 500 milisaniye bekle
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " kesintiye uğradı.");
}
}
}
}
public class ThreadOrnek1 {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.setName("Thread-1");
MyThread thread2 = new MyThread();
thread2.setName("Thread-2");
thread1.start(); // İş parçacığını başlat
thread2.start(); // İş parçacığını başlat
}
}
start()
metodu, iş parçacığını başlatır ve JVM'inrun()
metodunu ayrı bir iş parçacığında yürütmesini sağlar. Doğrudanrun()
metodunu çağırmak, kodu ana iş parçacığında yürütür ve çoklu iş parçacığı sağlamaz.
2. Runnable
Arayüzünü Uygulayarak
java.lang.Runnable
arayüzünü uygulamak, iş parçacığı oluşturmanın daha esnek bir yoludur. Bu yöntem, sınıfınızın başka bir sınıftan miras almasını engellemez (Java'da çoklu kalıtım yoktur).
class MyRunnable implements Runnable {
private String threadName;
public MyRunnable(String name) {
this.threadName = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(threadName + ": " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(threadName + " kesintiye uğradı.");
}
}
}
}
public class ThreadOrnek2 {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable("Runnable-1"));
Thread thread2 = new Thread(new MyRunnable("Runnable-2"));
thread1.start();
thread2.start();
}
}
İş Parçacığı Yaşam Döngüsü
Bir iş parçacığı, yaşam döngüsü boyunca farklı durumlar arasında geçiş yapar:
- New (Yeni): İş parçacığı nesnesi oluşturulduğunda bu durumdadır (
new Thread()
). - Runnable (Çalıştırılabilir):
start()
metodu çağrıldığında iş parçacığı bu duruma geçer. İşletim sistemi zamanlayıcısı tarafından çalıştırılmayı bekler. - Running (Çalışıyor): İş parçacığı zamanlayıcı tarafından seçildiğinde ve CPU üzerinde yürütüldüğünde bu durumdadır.
- Blocked/Waiting (Engellenmiş/Bekliyor): İş parçacığı bir kaynağı beklerken (örneğin, I/O işlemi, kilit) veya
sleep()
,wait()
,join()
gibi metotlar çağrıldığında bu duruma geçer. - Terminated (Sonlanmış):
run()
metodu tamamlandığında veya bir istisna fırlatıldığında iş parçacığı sonlanır.
İş Parçacığı Senkronizasyonu
Birden fazla iş parçacığı aynı paylaşılan kaynağa (değişken, dosya, veritabanı bağlantısı vb.) aynı anda erişmeye çalıştığında veri tutarsızlıkları meydana gelebilir. Bu durumu önlemek için iş parçacığı senkronizasyonu kullanılır.
synchronized
Anahtar Kelimesi
synchronized
anahtar kelimesi, bir metodu veya kod bloğunu aynı anda yalnızca bir iş parçacığının erişebileceği şekilde kilitlemek için kullanılır. Bu, kritik bölümlere eş zamanlı erişimi engeller.
synchronized
Metot: Bir metotsynchronized
olarak işaretlendiğinde, o metodun ait olduğu nesnenin kilidi alınır. Aynı anda sadece bir iş parçacığı bu metodu yürütebilir.class Sayac { private int count = 0; public synchronized void arttir() { count++; System.out.println(Thread.currentThread().getName() + ": " + count); } public int getCount() { return count; } } class SayacRunnable implements Runnable { private Sayac sayac; public SayacRunnable(Sayac sayac) { this.sayac = sayac; } @Override public void run() { for (int i = 0; i < 1000; i++) { sayac.arttir(); } } } public class SenkronizasyonOrnek { public static void main(String[] args) throws InterruptedException { Sayac sayac = new Sayac(); Thread t1 = new Thread(new SayacRunnable(sayac), "Thread-A"); Thread t2 = new Thread(new SayacRunnable(sayac), "Thread-B"); t1.start(); t2.start(); t1.join(); // t1'in bitmesini bekle t2.join(); // t2'nin bitmesini bekle System.out.println("Son Sayac Değeri: " + sayac.getCount()); // Beklenen: 2000 } }
synchronized
Blok: Daha ince taneli kontrol sağlamak için kullanılır. Belirli bir nesne üzerinde kilitlenir.class SayacBlok { private int count = 0; private Object lock = new Object(); // Kilit nesnesi public void arttir() { synchronized (lock) { // lock nesnesi üzerinde kilitlen count++; System.out.println(Thread.currentThread().getName() + ": " + count); } } public int getCount() { return count; } } // Kullanımı Sayac sınıfı ile aynıdır.
wait()
, notify()
, notifyAll()
Metotları
Bu metotlar, iş parçacıklarının belirli koşullar altında birbirleriyle iletişim kurmasını ve senkronize olmasını sağlar. Bu metotlar Object
sınıfının metotlarıdır ve yalnızca synchronized
blok veya metot içinde çağrılabilirler.
wait()
: İş parçacığını beklemeye alır ve nesnenin kilidini serbest bırakır.notify()
: Bekleyen iş parçacıklarından birini uyandırır.notifyAll()
: Bekleyen tüm iş parçacıklarını uyandırır.
join()
Metodu
join()
metodu, bir iş parçacığının tamamlanmasını beklemek için kullanılır. Çağıran iş parçacığı, join()
çağrısı yapılan iş parçacığı tamamlanana kadar bekler.
public class JoinOrnek {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("Çalışan İş Parçacığı: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
System.out.println("Ana iş parçacığı bekliyor...");
t.join(); // t iş parçacığının bitmesini bekle
System.out.println("Ana iş parçacığı devam ediyor.");
}
}
ExecutorService
ve Future
Java 5 ile birlikte gelen java.util.concurrent
paketi, iş parçacığı yönetimini kolaylaştıran daha yüksek seviyeli API'ler sunar. ExecutorService
, iş parçacığı havuzlarını yönetmek için kullanılırken, Future
asenkron işlemlerin sonuçlarını temsil eder.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
public class ExecutorServiceOrnek {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2); // 2 iş parçacıklı havuz
// Runnable görevi
executor.submit(() -> {
System.out.println("Runnable görevi çalışıyor.");
});
// Callable görevi (değer döndürür)
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("Callable görevi çalışıyor.");
Thread.sleep(1000);
return 123;
}
});
System.out.println("Görevin sonucu: " + future.get()); // Sonucu alana kadar bekle
executor.shutdown(); // Havuzu kapat
}
}
Çoklu iş parçacığı, modern Java uygulamaları için vazgeçilmez bir özelliktir. Ancak, senkronizasyon ve iş parçacığı güvenliği konularına dikkat etmek, karmaşık hataların önüne geçmek için hayati öneme sahiptir. Bir sonraki derste Veritabanı Bağlantısı (JDBC) konusunu inceleyeceğiz.