Apakah aman untuk mendapatkan nilai dari java.util.HashMap dari beberapa utas (tanpa modifikasi)?

138

Ada kasus di mana peta akan dibangun, dan begitu diinisialisasi, itu tidak akan pernah diubah lagi. Namun itu akan diakses (melalui get (kunci) saja) dari beberapa utas. Apakah aman untuk menggunakan java.util.HashMapcara ini?

(Saat ini, saya dengan senang hati menggunakan java.util.concurrent.ConcurrentHashMap, dan tidak memiliki kebutuhan yang terukur untuk meningkatkan kinerja, tetapi saya hanya ingin tahu apakah yang sederhana HashMapsudah cukup. Oleh karena itu, pertanyaan ini bukan "Yang mana yang harus saya gunakan?" Juga bukan pertanyaan kinerja. Sebaliknya, pertanyaannya adalah "Apakah itu aman?")

Dave L.
sumber
4
Banyak jawaban di sini yang benar tentang saling pengecualian dari menjalankan utas, tetapi salah mengenai pembaruan memori. Saya telah memilih naik / turun sesuai, tetapi masih ada banyak jawaban salah dengan suara positif.
Heath Borders
@Heath Borders, jika instance a secara statis diinisialisasi HashMap yang tidak dapat dimodifikasi, seharusnya aman untuk dibaca bersamaan (karena utas lainnya tidak dapat melewatkan pembaruan karena tidak ada pembaruan), bukan?
kaqqao
Jika diinisialisasi secara statis dan tidak pernah dimodifikasi di luar blok statis, maka mungkin ok karena semua inisialisasi statis disinkronisasi oleh ClassLoader. Itu layak pertanyaan terpisah sendiri. Saya masih akan menyinkronkannya secara eksplisit dan profil untuk memverifikasi bahwa itu menyebabkan masalah kinerja nyata.
Perbatasan Heath
@HeathBorders - apa yang Anda maksud dengan "pembaruan memori"? JVM adalah model formal yang mendefinisikan hal-hal seperti visibilitas, atomicity, hubungan sebelum terjadi , tetapi tidak menggunakan istilah seperti "pembaruan memori". Anda harus mengklarifikasi, lebih disukai menggunakan terminologi dari JLS.
BeeOnRope
2
@Dave - Saya berasumsi Anda tidak lagi mencari jawaban setelah 8 tahun, tetapi sebagai catatan, kebingungan utama di hampir semua jawaban adalah bahwa mereka fokus pada tindakan yang Anda lakukan pada objek peta . Anda telah menjelaskan bahwa Anda tidak pernah memodifikasi objek, jadi itu semua tidak relevan. Satu-satunya potensi "gotcha" adalah bagaimana Anda mempublikasikan referensi ke Map, yang tidak Anda jelaskan. Jika Anda tidak melakukannya dengan aman, itu tidak aman. Jika Anda melakukannya dengan aman, itu benar . Detail dalam jawaban saya.
BeeOnRope

Jawaban:

55

Idiom Anda aman jika dan hanya jika referensi ke HashMapyang aman diterbitkan . Daripada apa pun yang berkaitan dengan internal HashMapitu 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 HashMapdan 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 HashMapreferensi 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.MAPuntuk 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.MAPdan 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] :

  1. Tukar referensi melalui bidang yang dikunci dengan benar ( JLS 17.4.5 )
  2. Gunakan penginisialisasi statis untuk melakukan toko inisialisasi ( JLS 12.4 )
  3. Tukar referensi melalui bidang volatil ( JLS 17.4.5 ), atau sebagai konsekuensi dari aturan ini, melalui kelas AtomicX
  4. 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 MAPke:

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 MAPdan 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 MAPsecara dinamis saat runtime. Jika Anda tidak perlu melakukan itu, maka cukup deklarasikan MAPsebagai 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 finalbidang (termasuk static finaluntuk 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 finalbidang, misalnya, memungkinkan JVM untuk mengasumsikan nilainya tidak berubah selama umur program dan mengoptimalkannya dengan berat. Penggunaan finalbidang anggota memungkinkan sebagian besar arsitektur membaca bidang dengan cara yang setara dengan bidang biasa, membaca dan tidak menghambat optimalisasi lebih lanjut c .

Akhirnya, penggunaan volatilememang 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 HashMapseperti 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 synchronizedmetode untuk mendapatkan / mengatur, AtomicReferenceatau 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 finaldibaca - tetapi ini sangat jarang saat ini.

BeeOnRope
sumber
never modify HashMapbukan berarti state of the map objectthread aman saya pikir. Tuhan tahu implementasi perpustakaan, jika dokumen resmi tidak mengatakan itu aman.
Jiang YD
@JiangYD - Anda benar ada area abu-abu dalam beberapa kasus: ketika kita mengatakan "modifikasi" yang sebenarnya kita maksudkan adalah setiap tindakan yang secara internal melakukan beberapa penulisan yang mungkin berpacu dengan membaca atau menulis di utas lainnya. Tulisan-tulisan ini mungkin detail implementasi internal, sehingga bahkan sebuah operasi yang tampaknya "hanya baca" seperti get()sebenarnya dapat melakukan beberapa penulisan, misalnya memperbarui beberapa statistik (atau dalam hal akses-dipesan LinkedHashMapmemperbarui urutan akses). Jadi kelas yang ditulis dengan baik harus menyediakan beberapa dokumentasi yang membuatnya jelas jika ...
BeeOnRope
... tampaknya operasi "baca saja" benar-benar hanya dibaca secara internal dalam arti utas-keselamatan. Dalam pustaka standar C ++, misalnya, ada aturan selimut bahwa fungsi anggota yang ditandai constbenar-benar hanya-baca dalam arti seperti itu (secara internal, mereka mungkin masih melakukan penulisan, tetapi ini harus dibuat aman-utas). Tidak ada constkata kunci di Jawa dan saya tidak mengetahui adanya jaminan selimut yang didokumentasikan, tetapi secara umum kelas perpustakaan standar berperilaku seperti yang diharapkan, dan pengecualian didokumentasikan (lihat LinkedHashMapcontoh di mana RO ops seperti getsecara eksplisit disebutkan sebagai tidak aman).
BeeOnRope
@JiangYD - akhirnya, kembali ke pertanyaan awal Anda, karena HashMapkami benar-benar memiliki hak dalam dokumentasi perilaku utas keselamatan untuk kelas ini: Jika beberapa utas mengakses peta hash secara bersamaan, dan setidaknya satu utas mengubah peta secara struktural, itu harus disinkronkan secara eksternal. (Modifikasi struktural adalah operasi apa pun yang menambah atau menghapus satu atau lebih pemetaan; hanya mengubah nilai yang terkait dengan kunci yang sudah ada dalam instance bukan modifikasi struktural.)
BeeOnRope
Jadi untuk HashMapmetode yang kami harapkan hanya-baca, hanya-baca, karena mereka tidak secara struktural memodifikasi HashMap. Tentu saja, jaminan ini mungkin tidak berlaku untuk Mapimplementasi lain yang sewenang-wenang , tetapi pertanyaannya adalah tentang HashMapsecara spesifik.
BeeOnRope
70

Jeremy Manson, dewa ketika datang ke Java Memory Model, memiliki tiga bagian blog tentang topik ini - karena pada dasarnya Anda menanyakan pertanyaan "Apakah aman untuk mengakses HashMap yang tidak dapat diubah" - jawabannya adalah ya. Tetapi Anda harus menjawab predikat untuk pertanyaan itu yaitu - "Apakah HashMap saya tidak berubah". Jawabannya mungkin mengejutkan Anda - Java memiliki seperangkat aturan yang relatif rumit untuk menentukan imutabilitas.

Untuk info lebih lanjut tentang topik ini, baca posting blog Jeremy:

Bagian 1 tentang Ketidakmampuan di Jawa: http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html

Bagian 2 tentang Ketidakmampuan di Jawa: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html

Bagian 3 tentang Ketidakmampuan di Jawa: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.html

Taylor Gautier
sumber
3
Ini poin yang bagus, tapi saya mengandalkan inisialisasi statis, di mana tidak ada referensi lolos, jadi itu harus aman.
Dave L.
5
Saya gagal melihat bagaimana ini adalah jawaban yang berperingkat tinggi (atau bahkan jawaban). Untuk satu, tidak menjawab pertanyaan dan tidak menyebutkan satu prinsip utama yang akan memutuskan apakah aman atau tidak: publikasi yang aman . "Jawaban" bermuara pada "itu sangat sulit" dan di sini ada tiga tautan (rumit) yang bisa Anda baca.
BeeOnRope
Dia menjawab pertanyaan di akhir kalimat pertama. Dalam hal menjadi jawaban, dia mengajukan poin bahwa ketidakberdayaan (disinggung dalam paragraf pertama dari pertanyaan) tidak langsung, bersama dengan sumber daya berharga yang menjelaskan topik itu lebih jauh. Poin-poin tidak mengukur apakah itu jawaban, itu mengukur apakah jawaban itu "berguna" bagi orang lain. Jawaban yang diterima berarti itu adalah jawaban yang dicari OP, dan jawaban Anda diterima.
Jesse
@Jesse dia tidak menjawab pertanyaan di akhir kalimat pertama, dia menjawab pertanyaan "aman untuk mengakses objek yang tidak berubah", yang mungkin atau mungkin tidak berlaku untuk pertanyaan OP saat dia menunjukkan dalam kalimat berikutnya. Pada dasarnya ini adalah tipe jawaban yang hampir berupa tautan, "jawab sendiri", yang bukan jawaban yang baik untuk SO. Sedangkan untuk upvotes, saya pikir itu lebih merupakan fungsi menjadi 10,5 tahun dan topik yang sering dicari. Itu hanya menerima sedikit upvotes bersih dalam beberapa tahun terakhir jadi mungkin orang akan datang :).
BeeOnRope
35

Pembacaan aman dari sudut pandang sinkronisasi tetapi tidak dari sudut pandang memori. Ini adalah sesuatu yang banyak disalahpahami di antara pengembang Java termasuk di sini di Stackoverflow. (Perhatikan peringkat jawaban ini sebagai bukti.)

Jika Anda memiliki utas lain yang berjalan, mereka mungkin tidak melihat salinan terbaru dari HashMap jika tidak ada memori yang dihapus dari utas saat ini. Memori menulis terjadi melalui penggunaan kata kunci yang disinkronkan atau volatil, atau melalui penggunaan beberapa konstruksi konkurensi java.

Lihat artikel Brian Goetz pada Java Memory Model baru untuk detailnya.

Perbatasan Heath
sumber
Maaf untuk pengajuan ganda Heath, saya hanya memperhatikan milik Anda setelah saya mengirimkan milik saya. :)
Alexander
2
Saya senang ada orang lain di sini yang benar-benar memahami efek memori.
Heath Borders
1
Memang, meskipun tidak ada utas yang akan melihat objek sebelum diinisialisasi dengan benar, jadi saya tidak berpikir itu adalah masalah dalam kasus ini.
Dave L.
1
Itu sepenuhnya tergantung pada bagaimana objek diinisialisasi.
Bill Michell
1
Pertanyaannya mengatakan bahwa begitu HashMap telah diinisialisasi, ia tidak bermaksud memperbaruinya lebih lanjut. Mulai, kemudian, dia hanya ingin menggunakannya sebagai struktur data read-only. Saya pikir, akan aman untuk melakukannya, asalkan, data yang disimpan dalam Peta-nya tidak berubah.
Binita Bharati
9

Setelah sedikit lebih mencari, saya menemukan ini di java doc (penekanan milik saya):

Perhatikan bahwa implementasi ini tidak disinkronkan. Jika beberapa utas mengakses peta hash secara bersamaan, dan setidaknya satu utas mengubah peta secara struktural, ia harus disinkronkan secara eksternal. (Modifikasi struktural adalah operasi apa pun yang menambah atau menghapus satu atau lebih pemetaan; hanya mengubah nilai yang terkait dengan kunci yang instance sudah berisi bukan modifikasi struktural.)

Ini tampaknya menyiratkan bahwa itu akan aman, dengan asumsi kebalikan dari pernyataan itu benar.

Dave L.
sumber
1
Meskipun ini adalah saran yang sangat baik, seperti yang dinyatakan oleh jawaban lainnya, ada jawaban yang lebih bernuansa dalam kasus instance peta yang tidak dapat diubah dan diterbitkan dengan aman. Tetapi Anda harus melakukan itu hanya jika Anda Tahu Apa yang Anda Lakukan.
Alex Miller
1
Semoga dengan pertanyaan seperti ini lebih banyak dari kita yang bisa Tahu Apa yang Kami Lakukan.
Dave L.
Ini tidak benar. Sebagai jawaban lain menyatakan, harus ada terjadi-sebelum antara modifikasi terakhir dan semua "aman thread" selanjutnya berbunyi. Biasanya ini berarti Anda harus mempublikasikan objek dengan aman setelah dibuat dan modifikasinya dibuat. Lihat jawaban pertama yang ditandai benar.
markspace
9

Satu catatan adalah bahwa dalam beberapa keadaan, get () dari HashMap yang tidak disinkronkan dapat menyebabkan loop tak terbatas. Ini dapat terjadi jika put bersamaan () menyebabkan pengulangan peta.

http://lightbody.net/blog/2005/07/hashmapget_can_cause_an_infini.html

Alex Miller
sumber
1
Sebenarnya saya telah melihat ini menggantung JVM tanpa mengkonsumsi CPU (yang mungkin lebih buruk)
Peter Lawrey
2
Saya pikir kode ini telah ditulis ulang sehingga tidak mungkin lagi untuk mendapatkan infinite loop. Tetapi Anda tetap tidak harus mendapatkan dan menempatkan dari HashMap yang tidak disinkronkan karena alasan lain.
Alex Miller
@AlexMiller bahkan terlepas dari alasan lain (saya menganggap Anda mengacu pada penerbitan aman), saya tidak berpikir perubahan implementasi harus menjadi alasan untuk melonggarkan pembatasan akses, kecuali jika secara eksplisit diizinkan oleh dokumentasi. Seperti yang terjadi, HashMap Javadoc untuk Java 8 masih mengandung peringatan ini:Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally.
shmosel
8

Ada twist penting sekalipun. Aman untuk mengakses peta, tetapi secara umum tidak dijamin bahwa semua utas akan melihat kondisi yang sama persis (dan dengan demikian nilainya) dari HashMap. Ini mungkin terjadi pada sistem multiprosesor di mana modifikasi pada HashMap dilakukan oleh satu utas (misalnya, yang mengisi itu) dapat duduk dalam cache CPU dan tidak akan terlihat oleh utas yang berjalan pada CPU lain, sampai operasi pagar memori dilakukan memastikan koherensi cache. Spesifikasi Bahasa Jawa eksplisit untuk yang satu ini: solusinya adalah memperoleh kunci (disinkronkan (...)) yang memancarkan operasi pagar memori. Jadi, jika Anda yakin bahwa setelah mengisi HashMap, masing-masing utas memperoleh kunci APA PUN, maka sejak saat itu untuk mengakses HashMap dari utas apa pun hingga HashMap dimodifikasi lagi.

Alexander
sumber
Saya tidak yakin bahwa utas yang mengaksesnya akan memperoleh kunci apa pun, tetapi saya yakin mereka tidak akan mendapatkan referensi ke objek sampai setelah diinisialisasi, jadi saya tidak berpikir mereka dapat memiliki salinan basi.
Dave L.
@Alex: Referensi ke HashMap dapat berubah-ubah untuk membuat jaminan visibilitas memori yang sama. @ Dave: Ini adalah mungkin untuk melihat referensi OBJS baru sebelum pekerjaan ctor yang menjadi terlihat untuk thread Anda.
Chris Vest
@Christian Dalam kasus umum, tentu saja. Saya mengatakan bahwa dalam kode ini, tidak.
Dave L.
Mendapatkan kunci RANDOM tidak menjamin seluruh cache cpu utas untuk dihapus. Itu tergantung pada implementasi JVM, dan sangat mungkin tidak dilakukan dengan cara ini.
Pierre
Saya setuju dengan Pierre, saya tidak berpikir mendapatkan kunci saja sudah cukup. Anda harus melakukan sinkronisasi pada kunci yang sama agar perubahan dapat terlihat.
damluar
5

Menurut http://www.ibm.com/developerworks/java/library/j-jtp03304/ # Keamanan inisialisasi Anda dapat menjadikan HashMap Anda bidang terakhir dan setelah konstruktor selesai, ia akan diterbitkan dengan aman.

... Di bawah model memori baru, ada sesuatu yang mirip dengan hubungan sebelum terjadi antara penulisan bidang terakhir di konstruktor dan beban awal referensi bersama ke objek di thread lain. ...

bodrin
sumber
Jawaban ini berkualitas rendah, sama dengan jawaban dari @taylor gauthier tetapi dengan perincian yang lebih sedikit.
Snicolas
1
Ummmm ... bukan untuk menjadi keledai, tetapi Anda memilikinya mundur. Taylor berkata "tidak, lihat posting blog ini, jawabannya mungkin mengejutkan Anda", sedangkan jawaban ini benar-benar menambahkan sesuatu yang baru yang saya tidak tahu ... Tentang hubungan yang terjadi sebelum hubungan penulisan kolom terakhir dalam sebuah konstruktor. Jawaban ini sangat bagus, dan saya senang saya membacanya.
Ajax
Hah? Ini adalah satu - satunya jawaban yang benar yang saya temukan setelah menelusuri jawaban yang berperingkat lebih tinggi. Kuncinya diterbitkan dengan aman dan ini adalah satu-satunya jawaban yang bahkan menyebutkannya.
BeeOnRope
3

Jadi skenario yang Anda gambarkan adalah bahwa Anda perlu memasukkan banyak data ke dalam Peta, maka ketika Anda selesai mengisinya, Anda memperlakukannya sebagai tidak berubah. Salah satu pendekatan yang "aman" (yang berarti Anda menegakkan bahwa itu benar-benar dianggap abadi) adalah mengganti referensi dengan Collections.unmodifiableMap(originalMap)saat Anda siap untuk membuatnya tidak berubah.

Untuk contoh seberapa buruk peta bisa gagal jika digunakan secara bersamaan, dan solusi yang disarankan yang saya sebutkan, lihat entri parade bug ini: bug_id = 6423457

Akan
sumber
2
Ini "aman" karena ini memberlakukan immutabilitas, tetapi tidak membahas masalah keamanan utas. Jika peta aman untuk diakses dengan pembungkus UnmodifiableMap maka aman tanpa itu, dan sebaliknya.
Dave L.
2

Pertanyaan ini dibahas dalam buku "Java Concurrency in Practice" karya Brian Goetz (Listing 16.8, halaman 350):

@ThreadSafe
public class SafeStates {
    private final Map<String, String> states;

    public SafeStates() {
        states = new HashMap<String, String>();
        states.put("alaska", "AK");
        states.put("alabama", "AL");
        ...
        states.put("wyoming", "WY");
    }

    public String getAbbreviation(String s) {
        return states.get(s);
    }
}

Karena statesdinyatakan sebagai finaldan inisialisasi dilakukan dalam konstruktor kelas pemilik, utas apa pun yang kemudian membaca peta ini dijamin akan melihatnya pada saat konstruktor selesai, asalkan tidak ada utas lain yang akan mencoba mengubah isi peta.

escudero380
sumber
1

Berhati-hatilah bahwa bahkan dalam kode single-threaded, mengganti ConcurrentHashMap dengan HashMap mungkin tidak aman. ConcurrentHashMap melarang null sebagai kunci atau nilai. HashMap tidak melarang mereka (jangan tanya).

Jadi dalam situasi yang tidak mungkin kode Anda yang ada dapat menambahkan nol ke koleksi selama pengaturan (mungkin dalam beberapa kasus kegagalan), mengganti koleksi seperti yang dijelaskan akan mengubah perilaku fungsional.

Yang mengatakan, asalkan Anda tidak melakukan hal lain bersamaan membaca dari HashMap aman.

[Sunting: oleh "concurrent reads", maksud saya tidak ada modifikasi bersamaan.

Jawaban lain menjelaskan bagaimana memastikan ini. Salah satu caranya adalah membuat peta tidak berubah, tetapi itu tidak perlu. Sebagai contoh, model memori JSR133 secara eksplisit mendefinisikan mulai thread untuk menjadi tindakan yang disinkronkan, yang berarti bahwa perubahan yang dibuat pada thread A sebelum memulai thread B terlihat di thread B.

Maksud saya bukan untuk menentang jawaban yang lebih rinci tentang Model Memori Java. Jawaban ini dimaksudkan untuk menunjukkan bahwa selain dari masalah konkurensi, ada setidaknya satu perbedaan API antara ConcurrentHashMap dan HashMap, yang dapat mengacaukan bahkan sebuah program berulir tunggal yang menggantikan satu dengan yang lainnya.]

Steve Jessop
sumber
Terima kasih atas peringatannya, tetapi tidak ada upaya untuk menggunakan kunci atau nilai nol.
Dave L.
Kupikir tidak akan ada. nulls dalam koleksi adalah sudut gila Jawa.
Steve Jessop
Saya tidak setuju dengan jawaban ini. "Membaca bersamaan dari HashMap aman" dengan sendirinya tidak benar. Itu tidak menyatakan apakah pembacaan terjadi terhadap peta yang bisa berubah atau tidak berubah. Agar benar itu harus membaca "Membaca berbarengan dari HashMap abadi aman"
Taylor Gautier
2
Tidak sesuai dengan artikel yang Anda tautkan sendiri: persyaratannya adalah bahwa peta itu tidak boleh diubah (dan perubahan sebelumnya harus terlihat oleh semua utas pembaca), bukan karena itu tidak berubah (yang merupakan istilah teknis di Jawa dan merupakan kondisi yang memadai tetapi tidak perlu untuk keselamatan).
Steve Jessop
Juga sebuah catatan ... menginisialisasi kelas secara tersinkronisasi mensinkronkan pada kunci yang sama (ya, Anda dapat menemui jalan buntu dalam inisialisasi bidang statis), jadi jika inisialisasi Anda terjadi secara statis, tidak mungkin bagi orang lain untuk melihatnya sebelum inisialisasi selesai, karena mereka harus diblokir dalam metode ClassLoader.loadClass pada kunci yang sama diperoleh ... Dan jika Anda bertanya-tanya tentang classloader berbeda memiliki salinan berbeda dari bidang yang sama, Anda akan benar ... tetapi itu akan menjadi ortogonal ke pengertian kondisi ras; bidang statis classloader berbagi pagar memori.
Ajax
0

http://www.docjar.com/html/api/java/util/HashMap.java.html

di sini adalah sumber untuk HashMap. Seperti yang Anda tahu, sama sekali tidak ada kode penguncian / mutex di sana.

Ini berarti bahwa walaupun boleh untuk membaca dari HashMap dalam situasi multithreaded, saya pasti akan menggunakan ConcurrentHashMap jika ada beberapa penulisan.

Yang menarik adalah bahwa .NET HashTable dan Kamus <K, V> telah membangun kode sinkronisasi.

FlySwat
sumber
2
Saya pikir ada kelas di mana hanya dengan membaca secara bersamaan dapat membuat Anda mendapat masalah, karena penggunaan internal variabel instan misalnya, misalnya. Jadi orang mungkin perlu memeriksa sumber dengan hati-hati, lebih dari pemindaian cepat untuk mengunci / kode mutex.
Dave L.
0

Jika inisialisasi dan setiap put disinkronkan, Anda menyimpan.

Kode berikut disimpan karena classloader akan menangani sinkronisasi:

public static final HashMap<String, String> map = new HashMap<>();
static {
  map.put("A","A");

}

Kode berikut disimpan karena penulisan volatile akan menjaga sinkronisasi.

class Foo {
  volatile HashMap<String, String> map;
  public void init() {
    final HashMap<String, String> tmp = new HashMap<>();
    tmp.put("A","A");
    // writing to volatile has to be after the modification of the map
    this.map = tmp;
  }
}

Ini juga akan berfungsi jika variabel anggota final karena final juga volatile. Dan jika metodenya adalah konstruktor.

TomWolk
sumber