Java'da kullanılan bellek çitleri ne için kullanılır?


18

Sürüm 9'da Java SE'ye eklenen yeni bir sınıfın SubmissionPublisher( Java SE 10'daki kaynak kodu, OpenJDK | docs ) nasıl uygulandığını anlamaya çalışırken , daha VarHandleönce bilmediğim birkaç API çağrısına rastladım :

fullFence, acquireFence, releaseFence, loadLoadFenceVe storeStoreFence.

Biraz araştırma yaptıktan sonra, özellikle bellek bariyerleri / çitler kavramı hakkında (daha önce duymuştum, evet; ama onları hiç kullanmadılar, bu yüzden anlamlarına çok aşina değildim), bence ne için olduklarına dair temel bir anlayışa sahibim . Bununla birlikte, sorularım bir yanlış anlamadan kaynaklanabileceğinden, ilk etapta doğru yaptığımdan emin olmak istiyorum:

  1. Bellek engelleri okuma ve yazma işlemleriyle ilgili kısıtlamaları yeniden sıralamaktadır.

  2. Bellek engelleri iki ana kategoriye ayrılabilir: okuma veya yazma ya da her ikisi için sınırlama koymalarına bağlı olarak tek yönlü ve çift yönlü bellek engelleri.

  3. C ++ çeşitli bellek engellerini destekler , ancak bunlar tarafından sağlananlarla eşleşmez VarHandle. Bununla birlikte, bazı bellek bariyerleri , karşılık gelen C ++ bellek bariyerleri ile uyumlu olan sipariş efektleriVarHandle sağlar .

    • #fullFence ile uyumlu atomic_thread_fence(memory_order_seq_cst)
    • #acquireFence ile uyumlu atomic_thread_fence(memory_order_acquire)
    • #releaseFence ile uyumlu atomic_thread_fence(memory_order_release)
    • #loadLoadFenceve #storeStoreFenceuyumlu C ++ karşı parçası bulunmaz

Anlambilim, ayrıntılar söz konusu olduğunda açıkça farklılık gösterdiğinden, uyumlu kelime burada gerçekten önemli görünüyor. Örneğin, tüm C ++ engelleri çift yönlüdür, oysa Java engelleri (zorunlu olarak) değildir.

  1. Çoğu bellek engelinin senkronizasyon etkileri de vardır. Bunlar özellikle diğer bariyerlerde kullanılan bariyer tipine ve önceden yürütülen bariyer talimatlarına bağlıdır. Bir bariyer talimatının sahip olduğu tüm çıkarımlar donanıma özgü olduğundan, daha yüksek seviye (C ++) bariyerlere sadık kalacağım. Örneğin, C ++ 'da, bir ayırma bariyeri komutundan önce yapılan değişiklikler, bir edinme bariyeri talimatını yürüten bir iş parçacığı tarafından görülebilir .

Varsayımlarım doğru mu? Öyleyse, ortaya çıkan sorularım:

  1. Mevcut bellek engelleri VarHandleherhangi bir bellek senkronizasyonuna neden oluyor mu?

  2. Bellek senkronizasyonuna neden olup olmadıklarına bakılmaksızın, Java'da yeniden sıralama kısıtlamaları ne işe yarayabilir? Java Bellek Modeli, uçucu alanlar, kilitler veya VarHandlebenzeri #compareAndSetişlemler söz konusu olduğunda siparişle ilgili çok güçlü garantiler vermektedir .

Bir örnek arıyorsanız: Yukarıda belirtilen BufferedSubscription, bir iç sınıf SubmissionPublisher(yukarıda bağlantılı kaynak), 1079 satırında tam bir çit oluşturdu (işlev growAndAdd; bağlantılı web sitesi parça tanımlayıcılarını desteklemediğinden, sadece CTRL + F ). Ancak, bunun için ne olduğu benim için net değil.


1
Cevap vermeye çalıştım, ama çok basitleştirmek gerekirse, varlar çünkü insanlar Java'nın sahip olduğundan daha zayıf bir mod istiyorlar . Küçükten büyüğe, bu şöyle olacaktır: plain -> opaque -> release/acquire -> volatile (sequential consistency).
Eugene

Yanıtlar:


11

Bu çoğunlukla cevapsız, gerçekten (başlangıçta bir yorum yapmak istedim, ancak gördüğünüz gibi çok uzun). Sadece bunu kendim çok sorguladım, çok fazla okuma ve araştırma yaptım ve bu zamanda güvenle söyleyebilirim: bu karmaşık. Hatta nasıl çalıştıklarını (üretilen montaj koduna bakarken) anlamak için jcstress ile birden fazla test yazdım ve bazıları bir şekilde mantıklı olsa da, konu genel olarak kolay değil.

Anlamanız gereken ilk şey:

Java Dil Belirtimi (JLS) hiçbir yerde engellerden bahsetmez . Bu, java için bir uygulama detayı olacaktır: gerçekten semantikten önce gerçekleşmesi açısından hareket eder . Bunları JMM'ye (Java Bellek Modeli) göre uygun şekilde belirleyebilmek için, JMM'nin çok fazla değişmesi gerekecektir .

Bu devam eden bir çalışmadır.

İkincisi, eğer burada yüzeyi gerçekten çizmek istiyorsanız, bu ilk izlenecek şey . Konuşma inanılmaz. En sevdiğim bölüm, Herb Sutter 5 parmağını kaldırdığında ve "Bu, kaç insanın bunlarla gerçekten ve doğru bir şekilde çalışabileceğini" söylediğinde. Bu size karmaşıklığın bir ipucunu vermelidir. Bununla birlikte, kavranması kolay bazı önemsiz örnekler vardır ( diğer bellek garantilerini umursamayan, ancak yalnızca kendisinin doğru şekilde arttığına dikkat eden) birden çok iş parçacığı tarafından güncellenen bir sayaç gibi ).

Başka bir örnek, (java'da) bir volatilebayrağın iş parçacıklarının durmasını / başlamasını denetlemesini istediğiniz zamandır. Bilirsiniz, klasik:

volatile boolean stop = false; // on thread writes, one thread reads this    

Eğer java ile çalışıyorsanız, o bilemez olmadan volatile bu kod bozuldu (Çift kontrol kilitleme örneğin onsuz bozuldu neden okuyabilir). Ancak, yüksek performanslı kod yazan bazı insanlar için bunun çok fazla olduğunu da biliyor musunuz? volatileokuma / yazma da sıralı tutarlılığı garanti eder - bu bazı güçlü garantilere sahiptir ve bazı insanlar bunun daha zayıf bir versiyonunu ister.

Bir iş parçacığı güvenli bayrağı, ancak uçucu değil? Evet aynen: VarHandle::set/getOpaque.

Ve sorgulayamadığı neden Örneğin, bir kullanıcı o gerekebilir? Herkes a volatile. Tarafından domuzcuk destekli tüm değişikliklerle ilgilenmez .

Java'da bunu nasıl başaracağımızı görelim. Her şeyden önce, bu tür egzotik şeyler zaten API varolan: AtomicInteger::lazySet. Bu, Java Bellek Modelinde belirtilmemiş ve net bir tanımı yok ; hala insanlar bunu kullandı (LMAX, afaik ya da daha fazla okuma için ). IMHO, AtomicInteger::lazySetbir VarHandle::releaseFence(ya da VarHandle::storeStoreFence).


Birinin neden bunlara ihtiyacı olduğunu cevaplamaya çalışalım mı?

JMM'nin bir alana erişmek için temel olarak iki yolu vardır: düz ve uçucu ( sıralı tutarlılığı garanti eder ). Bahsettiğiniz tüm bu yöntemler, bu iki serbest bırakma / edinme semantiği arasına bir şey getirmek için vardır ; insanların gerçekten buna ihtiyaç duyduğu durumlar var sanırım .

Daha da fazla gelen gevşeme bırakma / edinilen olacağını opak olan tam olarak anlamak için hala çalışıyorum .


Sonuç olarak (anlayışınız oldukça doğrudur, btw): bunu java'da kullanmayı planlıyorsanız - şu anda herhangi bir spesifikasyonu yoktur, kendi sorumluluğunuzdadır. Onları anlamak istiyorsanız, C ++ eşdeğer modları başlangıç ​​noktasıdır.


1
lazySetEski belgelerle bağlantı kurarak anlamını anlamaya çalışmayın , mevcut belgeler bugünlerde ne anlama geldiğini kesin olarak söylüyor. Ayrıca, JMM'nin sadece iki erişim moduna sahip olduğunu söylemek yanıltıcıdır. Birlikte okumadan önce bir ilişki kurabilen uçucu okuma ve uçucu yazma var .
Holger

1
Bu konuda daha fazla bir şeyler yazmanın ortasındaydım. Cas'in tam bir bariyer gibi davranan bir okuma ve yazma olduğunu düşünün ve neden rahatlamak istediğini anlayabilirsiniz. Bir kilidi uygularken, ilk eylem kilit sayısında cas (0, 1) 'dir, ancak sadece semantik (uçucu okuma gibi) edinmeniz gerekir, oysa kilidin açılması için 0 son yazım semantik (uçucu yazma gibi) olmalıdır ), böylece kilit açma ve sonraki kilitleme arasında daha önce bir şey olur . Edinme / Bırakma, farklı kilitler kullanan dişler için Uçucu Okuma / Yazma'dan bile daha zayıftır.
Holger

1
@Peter Cordes: volatileAnahtar kelimeye sahip ilk C sürümü , Java'dan beş yıl sonra C99'du , ancak yine de yararlı anlambilimden yoksundu, C ++ 03'in bile Bellek Modeli yok. C ++ 'nın "atom" olarak adlandırdığı şeyler de Java'dan çok daha genç. Ve volatileanahtar kelime atom güncellemelerini bile ima etmiyor. Öyleyse neden böyle adlandırılmalı?
Holger

1
@PeterCordes belki de karıştırıyorum restrict, ancak __volatileanahtar kelime olmayan bir derleyici uzantısı kullanmak için yazmak zorunda kaldığım zamanları hatırlıyorum . Belki de, C89'u tamamen uygulamadı mı? Bana o yaşlı olduğumu söyleme . Java 5'ten önce, volatileC'ye çok daha yakındı. Ancak Java'nın MMIO'su yoktu, bu yüzden amacı her zaman çok iş parçacıklıydı, ancak Java 5 öncesi semantiği bunun için çok yararlı değildi. Semantik gibi serbest bırakma / elde etme eklendi, ancak yine de atomik değil (atomik güncellemeler üzerine ek bir özelliktir).
Holger

2
@Eugene ilişkin bu , benim örnek acquire olacağını kilitleme için cas kullanmak için özeldi. Bir geri sayım mandalı, serbest bırakma semantiği ile atomik azalmalar taşır, ardından iplik, bir edinme çitinin yerleştirilmesi ve son eylemin gerçekleştirilmesi sıfıra ulaşır. Tabii ki, tam çitin gerekli kaldığı atom güncellemeleri için başka durumlar da var.
Holger
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.