JavaProgramlamaKıdemli Java Geliştirici

**LockSupport**'ın hangi mimari özelliği, **unpark** işleminin **park** işleminden önce geldiği durumlarda kaybolan uyanmaları önler?

Hintsage yapay zeka asistanı ile mülakatları geçin

Sorunun Cevabı

Sorunun Geçmişi

Java 5'ten önce, thread koordinasyonu Thread.suspend gibi ilkel yöntemlere dayanıyordu (doğal deadlock riski nedeniyle kullanımdan kaldırıldı) veya Object.wait/notify yöntemine, bu da sıkı izleyici sahipliği gerektiriyordu ve bildirim kaybolması sorunları yaşanıyordu. Java 5'te java.util.concurrent'in tanıtılmasıyla (JSR 166), LockSupport, yüksek performanslı senkronizatörler (örneğin, AbstractQueuedSynchronizer) inşa etmeyi kolaylaştırmak amacıyla tasarlanmış düşük seviyeli bir engelleme kaldırma ilkesidir, içsel kilitlerin yükünü taşımadan.

Sorun

Eş zamanlı programlamada, klasik bir yarış durumu, bir işaretleme thread'inin hedef thread gerçekten park etmeden önce unpark mekanizmasını çağırdığında meydana gelir. Geleneksel durum değişkenleri ile bu işaret kaybolur ve hedef thread sonsuz bir şekilde uykuya dalar. Basit bir çözüm, izinleri biriktirmek için bir sayım semaforu kullanmak olabilir, ancak bu, üreticinin tüketiciyi aşmasının durumunda gereksiz karmaşıklık ve potansiyel kaynak sızıntılarına yol açar.

Çözüm

LockSupport, her bir thread ile ilişkilendirilmiş, birikmeyen, tek bitlik bir izin kullanır. Bu izin, atılabilir, thread'e özgü bir geçiş belgesi işlevi görür:

  • LockSupport.unpark, hedef thread’in mevcut durumuna bakmaksızın, izni atomik olarak 1 (verildi) durumuna getirir.
  • LockSupport.park, izni atomik olarak tüketir (0'a ayarlayarak) ve izin mevcut olduğunda hemen geri döner; aksi takdirde, bir izin verilene veya thread kesildiğine kadar bloklanır.

İzin birikmediği için (1'de doygunluk), fazla unpark edilmekten kaynaklı bellek sızıntılarını önlerken, park'tan önce verilen bir unpark'ın hatırlanmasını garanti eder, dolayısıyla kaybolan uyanma sorununu ortadan kaldırır ve bir olur öncesi ilişkisi aracılığıyla bunu sağlar.

import java.util.concurrent.locks.LockSupport; public class PermitExample { public static void main(String[] args) throws InterruptedException { Thread worker = new Thread(() -> { System.out.println("Worker: Başlangıçta çalışma..."); try { Thread.sleep(100); } catch (InterruptedException e) {} System.out.println("Worker: Park etmeye çalışıyor..."); LockSupport.park(); System.out.println("Worker: Başarıyla unpark edildi!"); }); worker.start(); // Worker gerçekten park etmeden önce işaret et Thread.sleep(50); System.out.println("Main: Worker park etmeden önce unpark çağırıyor"); LockSupport.unpark(worker); worker.join(); } }

Hayattan bir Durum

Sorun Tanımı

Yüksek frekanslı ticaret sisteminin sipariş eşleştirme motorunu tasarlarken, içe aktarılan kuyruk kapasitesine ulaşıldığında tüketici thread'lerinin işlemleri askıya alabileceği bir baskı mekanizması gerekliydi. Standart ReentrantLock ve Condition, işaretleme sırasında kuyruğun kilidinde rekabete neden oluyordu ve Object.wait/notify yüksek dönüş hızlarında kaybolan uyanma riski taşıyordu.

Değerlendirilen Farklı Çözümler

1. Object.wait/notifyAll

Bu yaklaşım, kuyruğun içsel kilidini kullanıyordu. Artıları: Standart monitörler kullanarak basit uygulama. Eksileri: Üreticinin notify çağırabilmesi için monitörü edinmesi gerekiyordu, bu da bir seriyleşme darboğazı oluşturuyordu. Daha kötü, eğer üretici, tüketici kuyruk boyutunu kontrol edip wait çağırdığı kısa zaman diliminde notify çağırırsa, işaret kaybolur ve tüketici kalıcı bir deadlock'a düşer.

2. Birden fazla Condition ile ReentrantLock

“Dolu” ve “boş” durumları için ayrı koşullar kullanmaya çalıştık. Artıları: İçsel kilitlerden daha esnek, seçici uyanmaları sağlıyor. Eksileri: İşaretleme (signalAll) için hâlâ kilit edinme gerekiyordu ve koşul kuyrukları arasında thread’leri doğru bir şekilde aktarmanın karmaşıklığı bakım yükü getiriyordu, temel kilitleme yükünü çözmüyordu.

3. Aşikar atomik durum ile LockSupport

Seçilen çözüm, "ilerlemeye izin" vermek için bir AtomicBoolean ve bloklama için LockSupport kullanıyordu. Kuyruk dolduğunda, tüketici atomik olarak bir "needsParking" bayrağını ayarladı ve sonra park etti. Üreticiler, bir öğeyi çıkardıktan sonra, bayrağı kontrol etti ve ayarlıysa unpark çağırdılar. Artıları: İşaretleme için hiç kilit gerektirmiyordu, uyanmalar sırasında rekabeti ortadan kaldırıyordu. Tek bitlik izin modeli, üretici park'tan önce nanosecond'lar içinde unpark'ı çağırsa bile (CPU zamanlaması nedeniyle) uyanmanın kaybolmamasını sağlıyordu.

Seçilen Çözüm ve Sonuç

LockSupport yaklaşımını seçtik. İşaretleme mekanizmasını kuyruğun yapısal kilidinden ayırarak, ağır yük altında üretici gecikmesini %40 oranında azalttık ve stres testleri sırasında gözlemlenen kaybolan uyanma senaryolarını ortadan kaldırdık. Aşikar durum yönetimi (unpark sonrasında koşulu çift kontrol etme) doğruluğu sağladı, park() işlevinin yanıltıcı uyanma sözleşmesine rağmen.


Adayların Sıklıkla Kaçırdığı Noktalar

Does LockSupport.park release ownership of monitors held by the thread?

Hayır. Bu, Object.wait()'den kritik bir farktır. Bir thread LockSupport.park çağırdığında, bekleme durumuna geçer fakat mevcut olarak sahip olduğu tüm monitörlerin mülkiyetini korur. Başka bir thread, bu monitörlerden birine (örneğin, aynı nesne üzerinde senkronize bir bloğa) girmeye çalışırsa engellenir ve yalnızca park edilmiş thread bunu serbest bırakacaksa bir deadlock'a yol açabilir. Adaylar genellikle park'ın wait gibi olduğunu ve kilitleri serbest bıraktığını varsayar; bu tamamen thread'e özgü bir zamanlayıcı ilkesidir.

What is the behavior of LockSupport.park when invoked on a thread whose interrupt status is set?

Bu yöntem, engellenmeden hemen döner ve kesme durumunu temizlemez. Bu, Object.wait()'ten temelde farklıdır; çünkü o, kesme durumunu temizler ve InterruptedException fırlatır. LockSupport ile, thread, kesintiyi kabul etmek istiyorsa kesme durumunu açıkça kontrol etmeli ve temizlemelidir (örneğin, Thread.interrupted() aracılığıyla). Bu tasarım, park'ın kesintiye uğramaz bağlamlarda veya kesintinin park izninden ayrı bir konu olarak ele alındığı durumlarda kullanılmasına olanak tanır.

How does LockSupport handle spurious wakeups, and how does this affect coding patterns?

LockSupport.park, "hiçbir nedenle" döndüğü belgelenmiştir (yanlış uyanma), ancak pratikte bu modern JVM'lerde nadirdir. İzinli uyanmanın (unpark) aksine, yanlış uyanmalar izni tüketmez. Bu nedenle, çağrıcının her zaman bir döngüde park etmenin neden olduğu durumu yeniden kontrol etmesi gerekir:

while (!canProceed()) { LockSupport.park(); }

Adaylar genellikle park'tan sonra durumu bir kez kontrol etmenin yetersiz olduğunu atlar; thread, yanlış bir şekilde uyanabilir (veya istenmeyen bir kesinti nedeniyle) unpark çağrısı olmaksızın, durum koşulunu yeniden değerlendirmeyi gerektirir. İzin, geçerli bir unpark'ın kaybolmasını sağlamış olsa da, yanlış uyanmaları önlemez.