Bölüm 9: İş Parçacıkları
Ç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-0veThread-1olur. - Öncelik oluşturan iş parçacığından (main, 5 =
NORM_PRIORITY) alınır;MAX_PRIORITY10'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
sleepiç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ı?
Doğru sonuç: 500 + 100 − 30 = 570. Senkronizasyon yok.
T1 okur: 500.
Bağlam değişimi: T2 de okur, o da 500 görür.
T2 çıkarır ve yazar: bakiye 470.
T1 bayat değerle devam eder: 500 + 100 = 600 hesaplar ...
... 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.
2 izinli semafor.
acquire(): izin vardı, 1'e iner.
tryAcquire(): izin var, alır ve true döner.
İzin yok: tryAcquire beklemeden false döner (acquire olsaydı sonsuza dek beklerdi).
Üç release: Semaphore izinlerin sahibini tutmaz ve başlangıç sayısını üst sınır saymaz; izin 3'e çıkar.
Yazdırılır.
Mandal 2'den geri sayar: iki countDown ile 0.
Sayaç 0'dayken countDown etkisizdir; eksiye inmez, geri de yüklenmez.
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'teykeninterrupt()gelir:sleepInterruptedExceptionfırlatır ve bunu yaparken kesme bayrağını temizler; bu yüzden catch içindeisInterrupted()false. “uyandı” yazılmaz. Kesmeyi yukarıya bildirmek isteyen kod catch içindeThread.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.