Idiom Anda aman jika dan hanya jika referensi ke HashMap
yang aman diterbitkan . Daripada apa pun yang berkaitan dengan internal HashMap
itu sendiri, publikasi yang aman berkaitan dengan bagaimana utas pembuatan membuat referensi ke peta terlihat oleh utas lainnya.
Pada dasarnya, satu-satunya ras yang mungkin ada di sini adalah antara konstruksi HashMap
dan semua utas pembacaan yang dapat mengaksesnya sebelum sepenuhnya dibangun. Sebagian besar diskusi adalah tentang apa yang terjadi pada keadaan objek peta, tetapi ini tidak relevan karena Anda tidak pernah memodifikasinya - jadi satu-satunya bagian yang menarik adalah bagaimana HashMap
referensi diterbitkan.
Misalnya, bayangkan Anda menerbitkan peta seperti ini:
class SomeClass {
public static HashMap<Object, Object> MAP;
public synchronized static setMap(HashMap<Object, Object> m) {
MAP = m;
}
}
... dan pada titik tertentu setMap()
disebut dengan peta, dan utas lainnya menggunakan SomeClass.MAP
untuk mengakses peta, dan periksa null seperti ini:
HashMap<Object,Object> map = SomeClass.MAP;
if (map != null) {
.. use the map
} else {
.. some default behavior
}
Ini tidak aman meskipun mungkin tampak seperti itu. Masalahnya adalah bahwa tidak ada hubungan sebelum-terjadi antara himpunan SomeObject.MAP
dan bacaan berikutnya pada utas lainnya, sehingga utas pembacaan bebas untuk melihat sebagian peta yang dibangun. Ini hampir dapat melakukan apa saja dan bahkan dalam praktiknya ia melakukan hal-hal seperti menempatkan utas membaca ke dalam lingkaran yang tak terbatas .
Untuk menerbitkan peta dengan aman, Anda perlu membangun hubungan sebelum-terjadi antara penulisan referensi ke HashMap
(yaitu, publikasi ) dan pembaca selanjutnya dari referensi itu (yaitu, konsumsi). Secara mudah, hanya ada beberapa cara yang mudah diingat untuk mencapai hal itu [1] :
- Tukar referensi melalui bidang yang dikunci dengan benar ( JLS 17.4.5 )
- Gunakan penginisialisasi statis untuk melakukan toko inisialisasi ( JLS 12.4 )
- Tukar referensi melalui bidang volatil ( JLS 17.4.5 ), atau sebagai konsekuensi dari aturan ini, melalui kelas AtomicX
- Inisialisasi nilai menjadi bidang terakhir ( JLS 17.5 ).
Yang paling menarik untuk skenario Anda adalah (2), (3) dan (4). Secara khusus, (3) berlaku langsung ke kode yang saya miliki di atas: jika Anda mengubah deklarasi MAP
ke:
public static volatile HashMap<Object, Object> MAP;
maka semuanya menjadi halal: pembaca yang melihat nilai non-nol tentu memiliki hubungan yang terjadi sebelum dengan toko MAP
dan karenanya melihat semua toko yang terkait dengan inisialisasi peta.
Metode lain mengubah semantik metode Anda, karena keduanya (2) (menggunakan initalizer statis) dan (4) (menggunakan final ) menyiratkan bahwa Anda tidak dapat mengatur MAP
secara dinamis saat runtime. Jika Anda tidak perlu melakukan itu, maka cukup deklarasikan MAP
sebagai a static final HashMap<>
dan Anda dijamin publikasi aman.
Dalam praktiknya, aturannya sederhana untuk akses aman ke "objek yang tidak pernah dimodifikasi":
Jika Anda menerbitkan objek yang tidak dapat diubah secara inheren (seperti yang dinyatakan dalam semua bidang final
) dan:
- Anda sudah dapat membuat objek yang akan ditugaskan pada saat deklarasi a : cukup gunakan
final
bidang (termasuk static final
untuk anggota statis).
- Anda ingin menetapkan objek nanti, setelah referensi sudah terlihat: gunakan bidang yang mudah menguap b .
Itu dia!
Dalam praktiknya, ini sangat efisien. Penggunaan static final
bidang, misalnya, memungkinkan JVM untuk mengasumsikan nilainya tidak berubah selama umur program dan mengoptimalkannya dengan berat. Penggunaan final
bidang anggota memungkinkan sebagian besar arsitektur membaca bidang dengan cara yang setara dengan bidang biasa, membaca dan tidak menghambat optimalisasi lebih lanjut c .
Akhirnya, penggunaan volatile
memang memiliki beberapa dampak: tidak ada penghalang perangkat keras yang diperlukan pada banyak arsitektur (seperti x86, khususnya yang tidak memungkinkan membaca lulus membaca), tetapi beberapa optimasi dan penataan ulang mungkin tidak terjadi pada waktu kompilasi - tetapi ini efeknya umumnya kecil. Sebagai gantinya, Anda benar-benar mendapatkan lebih dari yang Anda minta - Anda tidak hanya dapat mempublikasikannya dengan aman HashMap
, Anda dapat menyimpan lebih banyak lagi yang tidak dimodifikasi HashMap
seperti yang Anda inginkan dengan referensi yang sama dan yakinlah bahwa semua pembaca akan melihat peta yang diterbitkan dengan aman .
Untuk detail lebih banyak darah, lihat Shipilev atau FAQ ini oleh Manson dan Goetz .
[1] Secara langsung mengutip dari shipilev .
sebuah suara yang rumit, tapi apa yang saya maksud adalah bahwa Anda dapat menetapkan referensi pada saat konstruksi - baik pada titik deklarasi atau dalam konstruktor (bidang anggota) atau initializer statis (bidang statis).
b Secara opsional, Anda dapat menggunakan synchronized
metode untuk mendapatkan / mengatur, AtomicReference
atau sesuatu, tetapi kita sedang berbicara tentang pekerjaan minimum yang dapat Anda lakukan.
c Beberapa arsitektur dengan model memori yang sangat lemah (saya sedang melihat Anda , Alpha) mungkin memerlukan beberapa jenis pembatas baca sebelum final
dibaca - tetapi ini sangat jarang saat ini.