Bölüm 9: İş Parçacıkları

Bölüm 9: İş Parçacıkları · Çalışma Soruları
Süreç ve iş parçacığıThreadDurumlarstart ve runjoinYarış durumusynchronizedvolatileLockwait ve notifySemaphoreKesmeThreadLocal
Sercan KÜLCÜ

Çoktan Seçmeli · Temel Kavramlar

Soru 1: Ne Paylaşılır?

Aynı Java programındaki iki iş parçacığı için hangisi doğrudur?

A: İş parçacıkları sürecin adres alanını, yani heap'i ve sınıf alanını paylaşır; bu yüzden aynı nesneye erişebilir ve yarış durumu oluşabilir. Her iş parçacığının kendi yığını (yerel değişkenler, çağrı çerçeveleri) ve PC yazmacı vardır. Ayrı adres alanı süreçlere özgüdür.

Çıktı Sorusu · Thread Sınıfı

Soru 2: Ad, Öncelik, Daemon

Program main içinde çalışıyor ve daha önce başka iş parçacığı oluşturmadı. Ne yazdırır?

Thread a = new Thread(() -> { }, "isci");
Thread b = new Thread(() -> { });
Thread c = new Thread(() -> { });
c.setPriority(Thread.MAX_PRIORITY);
System.out.println(a.getName() + " "
    + b.getName() + " " + c.getName());
System.out.println(b.getPriority() + " "
    + c.getPriority() + " " + b.isDaemon());
System.out.println(Thread.currentThread().getName());
Cevabı göster
isci Thread-0 Thread-1
5 10 false
main
  • Ad verilen iş parçacığı otomatik numara almaz; adsız ilk ikisi Thread-0 ve Thread-1 olur.
  • Öncelik oluşturan iş parçacığından (main, 5 = NORM_PRIORITY) alınır; MAX_PRIORITY 10'dur. Daemon niteliği de oluşturandan kalıtılır: main daemon değildir.
  • currentThread() bu satırı çalıştıran iş parçacığıdır: main. a, b, c hiç başlatılmadı.

Çıktı Sorusu · Yaşam Döngüsü

Soru 3: Dört Durum ve İkinci start

uyu(ms), Thread.sleep'i try-catch ile saran bir yardımcıdır. Program ne yazdırır?

Thread t = new Thread(() -> uyu(300));
System.out.println(t.getState());
t.start();
Thread.sleep(100);
System.out.println(t.getState());
t.join();
System.out.println(t.getState() + " "
    + t.isAlive());
try {
  t.start();
} catch (IllegalThreadStateException e) {
  System.out.println("tekrar başlatılamaz");
}
Cevabı göster
NEW
TIMED_WAITING
TERMINATED false
tekrar başlatılamaz
  • Oluşturulmuş ama başlatılmamış: NEW.
  • 100 ms sonra t, 300 ms'lik sleep içindedir: TIMED_WAITING (süreli bekleme).
  • join() t bitene kadar bekler: TERMINATED, isAlive() false.
  • Biten bir iş parçacığı yeniden başlatılamaz: IllegalThreadStateException.

Adım Adım Çalıştırma · Oluşturma ve Başlatma

Soru 4: Önce run(), Sonra start()

Aynı Thread nesnesi önce run(), sonra start() ile çalıştırılıyor. Hangi iş parçacığı neyi yazdırır? Yığın paneli o anda çalışan iş parçacığının çağrı yığınını gösterir.

Thread nesnesi oluştu; adı T, durumu NEW. Henüz ayrı bir yığın yok.

t.run() sıradan bir metot çağrısıdır: yeni çerçeve main'in yığınına eklenir; currentThread() main'dir.

İş parçacığı hiç başlamadı: hâlâ NEW.

start(): JVM, T için yeni bir yığın açar ve run()'ı orada başlatır; start() hemen döner.

main join()'de bekler (WAITING). T kendi yığınında çalışır; currentThread() artık T.

T bitti, join() döndü. Çıktı her çalıştırmada aynıdır: run() çağrısı main'de, start() sonrası T'de, join() de sırayı sabitler.

Thread t = new Thread(() ->
    System.out.println("run: "
        + Thread.currentThread().getName()), "T");
t.run();
System.out.println("A " + t.getState());
t.start();
t.join();
System.out.println("B " + t.getState());

Çoktan Seçmeli · Oluşturma ve Başlatma

Soru 5: Zaten Türemiş Bir Sınıf

class Saat extends JLabel sınıfının nesnesi, her saniye kendi metnini güncelleyen bir iş parçacığında çalışacak. Hangi yaklaşım derlenir ve doğru çalışır?

B: Java tek sınıftan kalıtıma izin verir; extends JLabel, Thread derlenmez. Arayüz sayısında sınır olmadığından implements Runnable eklenir ve nesne bir Thread'e verilip start() ile başlatılır. run()'ı doğrudan çağırmak yeni iş parçacığı açmaz; kod çağıranın iş parçacığında çalışır.

Çoktan Seçmeli · Oluşturma ve Başlatma

Soru 6: Olası Çıktılar

Bu satırlar main içinde çalışıyor. Ekranda hangi çıktılar görülebilir?

Thread a = new Thread(() -> System.out.print("a"));
Thread b = new Thread(() -> System.out.print("b"));
a.start();
a.join();
b.start();
System.out.print("m");
b.join();

C: a.join() a bitmeden main'i ilerletmez: a her zaman ilk harftir. b başlatıldıktan sonra main'in m'yi yazması ile b'nin çalışması yarışır: abm ya da amb. b.join() yalnızca programın b'den önce bitmesini engeller. 300 denemede hep amb görüldü (main, yeni iş parçacığından önce davrandı); ama abm de geçerli bir sıradır, hiçbir sıra garanti edilmez.

Çıktı Sorusu · Yönetim ve Durdurma

Soru 7: join(ms) ve Sonucu Okumak

sonuc static bir int alandır (başta 0); uyu(ms) sleep'i saran yardımcıdır. Program ne yazdırır?

Thread h = new Thread(() -> {
  uyu(200);
  sonuc = 42;
});
h.start();
System.out.println("1: " + sonuc);
h.join(50);
System.out.println("2: " + sonuc + " "
    + h.isAlive());
h.join();
System.out.println("3: " + sonuc + " "
    + h.isAlive());
Cevabı göster
1: 0
2: 0 true
3: 42 false
  • h henüz 200 ms uyuyor: sonuç yazılmadı, 0.
  • join(50) en çok 50 ms bekler ve h bitmese de döner: hâlâ 0, h canlı.
  • join() süresiz bekler; h bitince yazdığı 42 main'e de görünür (join bir eşzamanlama noktasıdır).

Adım Adım Çalıştırma · Senkronizasyon

Soru 8: Kaybolan Para Çekme

Bakiye 500. T1 yatir(100), T2 cek(30) çağırıyor; bakiye += m ve bakiye -= m oku, hesapla, yaz adımlarından oluşur. Adımlardaki olası sırada bakiye sonunda kaç olur? Doğru sonuç kaç olmalıydı?

bakiye500T1 yazmacı—T2 yazmacı—

Doğru sonuç: 500 + 100 − 30 = 570. Senkronizasyon yok.

bakiye500T1 yazmacı500T2 yazmacı—

T1 okur: 500.

bakiye500T1 yazmacı500T2 yazmacı500

Bağlam değişimi: T2 de okur, o da 500 görür.

bakiye470T1 yazmacı500T2 yazmacı470

T2 çıkarır ve yazar: bakiye 470.

bakiye500T1 yazmacı600T2 yazmacı470

T1 bayat değerle devam eder: 500 + 100 = 600 hesaplar ...

bakiye600T1 yazmacı600T2 yazmacı470

... ve 600 yazar: T2'nin para çekmesi kayboldu. Bu sırada sonuç 600; ters sırada 470 de çıkabilirdi. Çözüm: iki metodu da synchronized yapmak (aynı monitör: this).

class Hesap {
  private int bakiye = 500;
  void yatir(int m) {
    bakiye += m;   // oku, ekle, yaz
  }
  void cek(int m) {
    bakiye -= m;   // oku, çıkar, yaz
  }
}

Çoktan Seçmeli · Senkronizasyon

Soru 9: Hangi Kilit?

class C içinde synchronized void p() ve static synchronized void q() var; x ve y iki ayrı C nesnesidir. Hangi iki çağrı farklı iş parçacıklarından aynı anda çalışamaz?

A: Örnek metot this nesnesinin monitörünü, static metot C.class nesnesinin monitörünü kilitler. İki x.p() aynı monitörü (x) ister: biri BLOCKED bekler. x ile y farklı monitörlerdir; C.q()'nun kilidi de örnek metotlarınkinden farklıdır, bu yüzden diğer çiftler birbirini engellemez.

Hata Bulma · Senkronizasyon

Soru 10: synchronized Ama Yine Yarış

İki iş parçacığı bu sınıfın iki ayrı nesnesini çalıştırıyor: new Thread(new Sayac()).start(); iki kez, ardından iki join(). run synchronized olduğu hâlde toplam neden 200000'den küçük çıkabiliyor? Düzeltin.

class Sayac implements Runnable {
  static int toplam = 0;
  public synchronized void run() {
    for (int i = 0; i < 100000; i++) {
      toplam++;
    }
  }
}
Cevabı göster

Her iş parçacığı kendi nesnesinin monitörünü (this) alır: iki farklı kilit birbirini engellemez, ama korunan veri ortak static toplam'dır. Üç çalıştırmada 134276, 158236, 186709 bulundu. Düzeltme: herkes aynı kilidi kullanır.

public void run() {
  for (int i = 0; i < 100000; i++) {
    synchronized (Sayac.class) {
      toplam++;
    }
  }
}

Ya da AtomicInteger ve incrementAndGet(). İki iş parçacığı aynı nesneyi paylaşsaydı özgün kod da doğru çalışırdı.

Çoktan Seçmeli · Görünürlük

Soru 11: volatile Ne Sağlar?

static volatile int sayac alanını iki iş parçacığı 100 000'er kez sayac++ ile artırıyor. Hangisi doğrudur?

D: volatile her okumanın ana bellekten yapılmasını ve her yazmanın hemen görünmesini garanti eder; bayrak gibi tek okuma/yazma için yeterlidir. sayac++ ise oku–ekle–yaz dizisidir; iki iş parçacığı araya girerse artış kaybolur. Sunumdaki denemede 163299, 164441, 199332 bulundu. Çözüm: AtomicInteger ya da synchronized.

Çoktan Seçmeli · Açık Kilitler

Soru 12: Yeniden Girişli Kilit

Program ne yazdırır?

ReentrantLock k = new ReentrantLock();
k.lock();
k.lock();
System.out.print(k.getHoldCount() + " ");
k.unlock();
System.out.print(k.isLocked() + " ");
k.unlock();
System.out.println(k.isLocked());

D: ReentrantLock, kilidi tutan iş parçacığının aynı kilidi tekrar almasına izin verir; yalnızca tutma sayısı artar: 2. Bir unlock() sayıyı 1'e indirir, kilit hâlâ main'dedir (isLocked() true). İkinci unlock() ile sayı 0 olur ve kilit bırakılır. Her lock() için bir unlock() gerekir; fazladan bir unlock() IllegalMonitorStateException fırlatırdı.

Hata Bulma · Açık Kilitler

Soru 13: finally Olmadan unlock

t1 bol(10, 0), ardından (t1 bittikten sonra) t2 bol(10, 2) çağırıyor. Ekranda ne görülür, t2 hangi durumda kalır? Hatayı düzeltin.

static final Lock kilit = new ReentrantLock();
static void bol(int a, int b) {
  kilit.lock();
  System.out.println(a / b);
  kilit.unlock();
}
Cevabı göster

t1'de 10 / 0 ArithmeticException fırlatır; t1 sonlanır ama unlock() satırına hiç gelinmez, kilit kilitli kalır. Ekranda yalnızca t1'in yığın izi görünür; 5 hiç yazılmaz. t2 lock()'ta sonsuza dek bekler. Denemede durumu WAITING çıktı: Lock bekleyişi monitör kilidi olmadığı için BLOCKED değil, WAITING'dir.

static void bol(int a, int b) {
  kilit.lock();
  try {
    System.out.println(a / b);
  } finally {
    kilit.unlock();   // istisnada da çalışır
  }
}

synchronized blokta bu sorun olmaz: bloktan istisnayla çıkılsa da monitör kendiliğinden bırakılır.

Çoktan Seçmeli · Koşul Senkronizasyonu

Soru 14: wait() ve sleep()

synchronized bir blok içinde kilit.wait() ile Thread.sleep(1000) arasındaki fark hangisidir?

B: Koşul beklerken kilidi tutmak, koşulu değiştirecek iş parçacığının içeri girmesini engellerdi; bu yüzden wait() kilidi bırakır ve notify/notifyAll ile uyanınca kilidi yeniden alıp devam eder. sleep() kilitle ilgilenmez. wait(ms) biçimi de vardır; wait() synchronized dışında çağrılırsa IllegalMonitorStateException fırlar.

Kod Yazma · Koşul Senkronizasyonu

Soru 15: ArrayBlockingQueue ile Toplam

Kapasitesi 2 olan ArrayBlockingQueue ile: üretici iş parçacığı 1–5'i, sonra bitiş işareti −1'i koysun; main −1 gelene kadar alıp toplasın. Kodu yazın; çıktı nedir?

Cevabı göster
BlockingQueue<Integer> q = new ArrayBlockingQueue<>(2);
Thread uretici = new Thread(() -> {
  try {
    for (int i = 1; i <= 5; i++) q.put(i);  // doluysa bekler
    q.put(-1);                               // bitiş işareti
  } catch (InterruptedException e) { }
});
uretici.start();
int toplam = 0;
while (true) {
  int v = q.take();                         // boşsa bekler
  if (v < 0) break;
  toplam += v;
}
uretici.join();
System.out.println("Toplam: " + toplam);

Çıktı her zaman Toplam: 15. put ve take beklemeyi ve uyandırmayı kendisi yapar. Kuyruk FIFO olduğu için −1 en son gelir.

Çoktan Seçmeli · Senkronizasyon Nesneleri

Soru 16: Doğru Araç

Bir oyunda 4 oyuncu iş parçacığı her turun sonunda birbirini bekleyecek, dördü de gelince tur skorları bir kez hesaplanacak ve hepsi bir sonraki tura birlikte geçecek. Bu her tur tekrarlanacak. Hangi araç uygundur?

C: CyclicBarrier, parti dolana kadar herkesi await()'te bekletir; dolunca bariyer eylemini bir kez çalıştırır ve herkesi bırakır. Yeniden kullanılabildiği için her tur aynı bariyerle beklenir. CountDownLatch tek kullanımlıktır, sayaç geri yüklenmez; Semaphore eş zamanlı girişi sınırlar; Exchanger yalnızca iki iş parçacığı arasında takas yapar.

Adım Adım Çalıştırma · Senkronizasyon Nesneleri

Soru 17: Semaphore ve CountDownLatch

Tek iş parçacığında çalışan bu kod ne yazdırır? İzin ve sayaçların her adımdaki değerini izleyin.

izinler

2 izinli semafor.

izinler

acquire(): izin vardı, 1'e iner.

izinler

tryAcquire(): izin var, alır ve true döner.

izinler

İzin yok: tryAcquire beklemeden false döner (acquire olsaydı sonsuza dek beklerdi).

izinler

Üç release: Semaphore izinlerin sahibini tutmaz ve başlangıç sayısını üst sınır saymaz; izin 3'e çıkar.

izinler

Yazdırılır.

izinlermandal sayacı0

Mandal 2'den geri sayar: iki countDown ile 0.

izinlermandal sayacı0

Sayaç 0'dayken countDown etkisizdir; eksiye inmez, geri de yüklenmez.

izinlermandal sayacı0

Mandal açık: await beklemeden true döner (süre dolsaydı false dönerdi).

Semaphore s = new Semaphore(2);
s.acquire();
boolean x1 = s.tryAcquire();
boolean x2 = s.tryAcquire();
s.release();
s.release();
s.release();
System.out.println(x1 + " " + x2 + " "
    + s.availablePermits());
CountDownLatch m = new CountDownLatch(2);
m.countDown();
m.countDown();
m.countDown();
System.out.println(m.getCount() + " "
    + m.await(10, TimeUnit.MILLISECONDS));

Çıktı Sorusu · Diğer Araçlar

Soru 18: Kesme Bayrağı

Program ne yazdırır?

Thread t = new Thread(() -> {
  try {
    Thread.sleep(5000);
    System.out.println("uyandı");
  } catch (InterruptedException e) {
    System.out.println("kesildi " + Thread
        .currentThread().isInterrupted());
  }
});
t.start();
Thread.sleep(100);
t.interrupt();
t.join();
Thread.currentThread().interrupt();
System.out.println(Thread.interrupted() + " "
    + Thread.interrupted());
Cevabı göster
kesildi false
true false
  • t 5 saniyelik sleep'teyken interrupt() gelir: sleep InterruptedException fırlatır ve bunu yaparken kesme bayrağını temizler; bu yüzden catch içinde isInterrupted() false. “uyandı” yazılmaz. Kesmeyi yukarıya bildirmek isteyen kod catch içinde Thread.currentThread().interrupt() ile bayrağı yeniden kurar.
  • main kendi bayrağını kurar. static Thread.interrupted() bayrağı okur ve temizler: ilk çağrı true, ikincisi false.

Çoktan Seçmeli · Diğer Araçlar

Soru 19: ThreadLocal Kopyaları

Program ne yazdırır?

ThreadLocal<Integer> t =
    ThreadLocal.withInitial(() -> 0);
t.set(5);
Thread x = new Thread(() -> {
  t.set(t.get() + 1);
  System.out.println("x: " + t.get());
});
x.start();
x.join();
System.out.println("main: " + t.get());

A: Her iş parçacığı ThreadLocal'ın kendi kopyasını görür. main'in 5'i x'e geçmez; x ilk get()'te withInitial'daki 0 ile başlar ve 1 yapar. x'in yazdığı da main'in kopyasını etkilemez: main 5 görür. Paylaşılmayan veri yarışmaz.

Son

Bölüm 9: İş Parçacıkları: Çalışma Soruları
Konu anlatımı: İş parçacıkları sunumu · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.