JavaProgramlamaJava Geliştirici

Sınıf başlatma sırasında belirlenen hangi özel happens-before ilişkisi, talep üzerine başlatıcı kalıbında güvenli yayılımı garanti eder?

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

Sorunun Cevabı.

Garanti, Java Bellek Modeli'nin (JMM) sınıf başlatma ile ilişkili happens-before kuralından kaynaklanmaktadır. JVM bir sınıfın statik alanına veya yöntemine ilk eriştiğinde, önce o sınıfın başlatma aşamasını tamamlamak zorundadır. Bu aşama, o sınıf nesnesine özgü iç bir kilit altında statik başlatıcı blokları ve alan atamalarını gerçekleştirir. Sonuç olarak, statik başlatıcıda gerçekleştirilen herhangi bir yazma işlemi —örneğin singleton örneğinin oluşturulması—, sınıfa erişen iş parçacıkları tarafından bu alanın takip eden herhangi bir okuma işlemi ile bir happens-before kenarı oluşturur ve yapılandırılmış durumun tam görünürlüğünü garanti eder, bu da eşzamanlı anahtar kelimeleri veya volatile beyanları gerektirmez.

public class ConnectionPool { private ConnectionPool() { // masraflı TCP el sıkışması ve iş parçacığı başlatma } private static class Holder { static final ConnectionPool INSTANCE = new ConnectionPool(); } public static ConnectionPool getInstance() { return Holder.INSTANCE; // Holder sınıfının başlatılmasını tetikler } }

Hayattan Bir Durum

Problem: Bir finansal ticaret uygulaması, maliyeti yüksek olan bir ConnectionPool singleton ölçütü gerektiriyordu, çünkü ilk TCP el sıkışmaları ve iş parçacığı başlatma pahalıydı, ancak belirli hafif teşhis modlarında ihtiyaç duyulmayabilir. İhtiyaç duymadığında, kullanılmadığı durumda bile başlatma sırasında yüzlerce milisaniye kaybettiren erken başlatma, Çift Kontrol Kilitleme ise volatile anlamlarının ve düzenleme engellerinin dikkatlice yönetilmesini gerektiriyordu, bu da talimat yeniden sıralamasını önlemek için.

Çözüm 1: Erken Başlatma: Bu yaklaşım, sınıf yüklendiğinde statik alanı başlatır, bu uygulanması basit ve JVM tarafından garanti edilen iş parçacığı güvenliğidir. Ancak, havuz asla erişilmediği durumlarda yapılandırma maliyetinden kaçınma gereğini yerine getiremez, teşhis modlarında önemli kaynakları israf eder ve dağıtım başlatma süresini gereksiz yere artırır.

Çözüm 2: Eşzamanlı Erişim: Getter'ı eşzamanlı sarmak, tüm iş parçacıkları arasında güvenlik sağlar ve kodlaması basittir. Ne yazık ki, her çağrıyı bir izleme kilidi almak zorunda bırakır, bu da yüksek frekanslı ticaret yükü altında, mikro saniyelerin önemli olduğu durumlarda ağır bir darboğaz oluşturur ve iş parçacıkları aynı kilidi paylaşır.

Çözüm 3: Talep Üzerine Başlatıcı: Bu, bir static final ConnectionPool örneği içeren özel bir statik sınıf ConnectionPoolHolder tanımlar, burada getInstance basitçe ConnectionPoolHolder.INSTANCE döner. Bu, JVM'nin tembel sınıf yüklemesini kullanır: Holder sınıfı yalnızca getInstance çağrıldığında başlatılır ve sınıf başlatma kilidi, açık bir senkronizasyon veya volatile yükümlülüğü olmadan güvenli yayılımı garanti eder.

Seçilen Çözüm: Ekip, sıfır yük sonrası başlatma performansı ve Java Bellek Modeli altında garanti edilen güvenliği nedeniyle başlatıcı kalıbını seçti, çünkü bu, tembel başlatmayı çalışma zamanı verimliliği ile mükemmel bir şekilde dengeledi.

Sonuç: Uygulama, ilk kullanımına kadar ağır başlatmayı erteleyerek yarış koşullarından uzak durarak ve yüksek hacimli ticaret oturumları sırasında başlatma yükünü ortadan kaldırarak havuz referansı için alt mikro saniye erişim gecikmesi sağladı.

Adayların Sıkça Gözden Kaçırdığı Şeyler


Çoklu iş parçacıklara sahip bir durumda singleton yapıcı, holder sınıfı başlatma sırasında bir istisna fırlatırsa sonraki iş parçacıkları ne olur?

Eğer statik başlatıcı bir istisna fırlatırsa, JVM sınıfı başlatmakta başarısız olarak işaret eder ve bir ExceptionInInitializerError (nedeni kapsayarak) fırlatır. Kritik olarak, ConnectionPoolHolder'a erişmeye çalışan sonraki bir iş parçacığı, kök neden geçici olsa bile (örneğin geçici ağ erişimsizliği gibi) bir NoClassDefFoundError alır. Çift Kontrol Kilitleme ile potansiyel olarak catch bloklarında inşayı yeniden denemek mümkün olmasına rağmen, holder kalıbı, sınıfın tanımlayıcı ClassLoader'ının ömrü boyunca başarısız bir başlatma durumunda kalması nedeniyle harici bir kurtarma mantığı gerektirir.


Talep üzerine başlatıcı kalıbı, çoklu kiracılı konteyner içinde örnek kapsamlı singleton'lara uyarlanabilir mi?

Hayır. Kalıp yalnızca statik alanlar ve sınıf düzeyi başlatma kilitlerine dayanır. Örnek kapsamlı veya kiracı başına singleton'lar için, holder'ın kiracı bağlamının iç sınıfı olması gerekir, ancak sınıf başlatma kilitleri her ClassLoader için geçerlidir, konteyner örneği başına değil. Bu, ya kiracılar arasında örneklerin paylaşımına (bir güvenlik ve izolasyon riski) ya da kiracı örneğinde açık senkronizasyon gerektirmeye yol açar, bu da kalıbın kilitten bağımsız erişim amacını bozar. Adaylar genellikle sınıf düzeyi tembel yüklemeleri nesne düzeyi tembel yüklemelerle karıştırır.


Bu kalıp, uygulama sunucusu ortamlarında birden fazla ClassLoader hiyerarşisi ile nasıl davranır?

Her ClassLoader, holder sınıfının kendi kopyasını bağımsız olarak başlatır. Tomcat veya WildFly üzerinde, eğer singleton sınıfı hem web uygulamasında hem de paylaşılan üst yükleyicide mevcutsa veya web uygulaması yeniden dağıtıldığında (yeni bir ClassLoader oluşturur), farklı örnekler var olacaktır. Bu, JVM süreci boyunca singleton sözleşmesini ihlal eder. Kalıp, tek bir sınıf yükleme ad alanında iş parçacığı güvenliğini garanti eder, ancak modüler ortamlarda sınıf yükleyici izolasyonunun uygulandığı kritik bir ayrım olan global JVM singleton kavramını sağlamaz.