Sizin deyim güvenlidir ancak ve ancak referans HashMap
almaktadır güvenle yayınlanan . İç yayınlarıyla ilgili her şeyden ziyade HashMap
, güvenli yayın , yapım iş parçacığının haritaya referansı diğer iş parçacıkları tarafından nasıl görünür hale getirdiği ile ilgilidir.
Temel olarak, burada mümkün olan tek yarış HashMap
, tamamen inşa edilmeden önce ona erişebilecek herhangi bir okuma iş parçacığının yapımı arasındadır. Tartışmanın çoğu, harita nesnesinin durumuna ne olduğu ile ilgilidir, ancak asla değiştirmediğiniz için bu önemsizdir - bu yüzden tek ilginç kısım HashMap
referansın nasıl yayınlandığıdır.
Örneğin, haritayı şu şekilde yayınladığınızı düşünün:
class SomeClass {
public static HashMap<Object, Object> MAP;
public synchronized static setMap(HashMap<Object, Object> m) {
MAP = m;
}
}
... ve bir noktada setMap()
bir harita ile çağrılır ve diğer iş parçacıkları SomeClass.MAP
haritaya erişmek için kullanılır ve şu şekilde null olup olmadığını kontrol edin:
HashMap<Object,Object> map = SomeClass.MAP;
if (map != null) {
.. use the map
} else {
.. some default behavior
}
Bu güvenli değil o sanki muhtemelen görünmesine rağmen. Sorun, başka bir iş parçacığının kümesi ve sonraki okuma arasında önceden gerçekleşen bir ilişki olmamasıdır SomeObject.MAP
, bu nedenle okuma iş parçacığı kısmen oluşturulmuş bir harita görmekte serbesttir. Bu hemen hemen her şeyi yapabilir ve pratikte bile okuma ipliğini sonsuz bir döngüye koymak gibi şeyler yapar .
Güvenle haritayı yayınlamak için bir tesis gerekir olur-öncesi arasındaki ilişkinin referans yazılı etmek HashMap
(yani yayına ) ve referans (yani tüketim) müteakip okuyucuları. Uygun bir şekilde, bunu başarmanın kolay hatırlanabilir birkaç yolu vardır [1] :
- Referansı uygun şekilde kilitlenmiş bir alanda değiştirin ( JLS 17.4.5 )
- İlklendirme depolarını yapmak için statik başlatıcı kullanın ( JLS 12.4 )
- Referansı değişken bir alanla ( JLS 17.4.5 ) veya bu kuralın bir sonucu olarak AtomicX sınıfları aracılığıyla değiştirin
- Değeri bir son alana başlatın ( JLS 17.5 ).
Senaryonuz için en ilginç olanlar (2), (3) ve (4). Özellikle, (3) doğrudan yukarıdaki kodum için geçerlidir: beyanı dönüştürürseniz MAP
:
public static volatile HashMap<Object, Object> MAP;
o zaman her şey koşerdir: null olmayan bir değer gören okuyucular mutlaka mağaza ile önce bir ilişki MAP
kurarlar ve böylece harita başlatma ile ilişkili tüm mağazaları görürler.
Diğer yöntemler yönteminizin anlambilimini değiştirir, çünkü (2) (statik initalizer kullanarak) ve (4) ( final kullanarak ) MAP
çalışma zamanında dinamik olarak ayarlayamayacağınızı ima eder . Eğer yoksa ihtiyaç bunu, o zaman sadece beyan MAP
bir şekilde static final HashMap<>
ve güvenli yayın garantilidir.
Uygulamada, kurallar "hiç değiştirilmemiş nesnelere" güvenli erişim için basittir:
Doğal olarak değiştirilemeyen (beyan edilen tüm alanlarda olduğu gibi) bir nesne yayınlıyorsanız final
ve:
- Zaten beyanı anında atanacak nesneyi oluşturabilirsiniz a : sadece kullanmak
final
(dahil alanını static final
statik üyeleri için).
- Nesneyi daha sonra, referans zaten göründükten sonra atamak istiyorsunuz: uçucu bir alan kullanın b .
Bu kadar!
Uygulamada çok verimlidir. static final
Örneğin, bir alanın kullanılması JVM'nin programın ömrü boyunca değerin değişmediğini varsaymasına ve onu yoğun şekilde optimize etmesine izin verir. final
Üye alanının kullanımı, çoğu mimarinin alanı normal alan okumasına eşdeğer bir şekilde okumasına olanak tanır ve daha fazla optimizasyonu engellemez c .
Son olarak, kullanımının bir volatile
etkisi vardır: pek çok mimaride donanım engeli gerekmez (özellikle x86, özellikle okumaların okumaları geçmesine izin vermeyenler), ancak derleme zamanında bazı optimizasyon ve yeniden sıralama gerçekleşmeyebilir - ancak bu etkisi genellikle küçüktür. Buna karşılık, aslında istediğinizden daha fazlasını elde edersiniz - sadece güvenli bir şekilde yayınlamakla HashMap
kalmaz HashMap
, aynı referansta istediğiniz kadar çok değiştirilmemiş s kaydedebilir ve tüm okuyucuların güvenli bir şekilde yayınlanmış bir harita göreceğinden emin olabilirsiniz. .
Daha fazla ayrıntı için Shipilev'e veya Manson ve Goetz tarafından hazırlanan bu SSS'ye bakın .
[1] Doğrudan shipilev'den alıntı .
a Kulağa karmaşık geliyor, ama demek istediğim referansı inşaat zamanında atayabilirsiniz - açıklama noktasında veya yapıcıda (üye alanları) veya statik başlatıcıda (statik alanlar).
b İsteğe bağlı olarak, synchronized
almak / ayarlamak için bir yöntem veya bir AtomicReference
veya bir şey kullanabilirsiniz, ancak yapabileceğiniz minimum işten bahsediyoruz.
c çok zayıf hafıza modelleriyle (Benim baktığım Bazı mimariler size , Alpha) bir önceki okuma bariyer çeşit gerektirebilir final
okuma - ama bunlar bugün çok nadirdir.