Apakah thread HashMap aman untuk kunci yang berbeda?

87

Jika saya memiliki dua utas ganda yang mengakses HashMap, tetapi menjamin bahwa mereka tidak akan pernah mengakses kunci yang sama pada saat yang sama, apakah hal itu masih dapat menyebabkan kondisi balapan?

Helder S Ribeiro
sumber

Jawaban:

100

Dalam jawaban @ dotsid dia mengatakan ini:

Jika Anda mengubah HashMap dengan cara apa pun, kode Anda akan rusak.

Dia benar. HashMap yang diperbarui tanpa sinkronisasi akan rusak meskipun utas menggunakan sekumpulan kunci yang terputus-putus. Berikut beberapa hal yang bisa salah.

  • Jika satu thread melakukan a put, thread lain mungkin melihat nilai usang untuk ukuran hashmap.

  • Saat utas melakukan tindakan putyang memicu pembuatan ulang tabel, utas lain mungkin melihat versi sementara atau usang dari referensi larik hashtable, ukurannya, kontennya, atau rantai hash. Kekacauan mungkin terjadi.

  • Ketika utas melakukan a putuntuk kunci yang bertabrakan dengan beberapa kunci yang digunakan oleh utas lain, dan utas terakhir melakukan a putuntuk kuncinya, maka utas terakhir mungkin melihat salinan usang dari referensi rantai hash. Kekacauan mungkin terjadi.

  • Ketika satu thread memeriksa tabel dengan kunci yang bertabrakan dengan salah satu kunci thread lainnya, ia mungkin menemukan kunci tersebut pada rantai. Ini akan memanggil sama dengan pada kunci itu, dan jika utas tidak disinkronkan, metode sama mungkin mengalami status basi di kunci itu.

Dan jika Anda memiliki dua utas yang melakukan putatau removememinta secara bersamaan , ada banyak peluang untuk kondisi balapan.

Saya dapat memikirkan tiga solusi:

  1. Gunakan a ConcurrentHashMap.
  2. Gunakan yang biasa HashMaptetapi sinkronkan di luar; misalnya menggunakan mutex primitif, Lockobjek, dan sebagainya.
  3. Gunakan yang berbeda HashMapuntuk setiap utas. Jika utas benar-benar memiliki sekumpulan kunci yang terputus-putus, maka seharusnya tidak perlu (dari perspektif algoritmik) bagi mereka untuk berbagi satu Peta. Memang, jika algoritme Anda melibatkan utas yang mengiterasi kunci, nilai, atau entri peta di beberapa titik, membagi satu peta menjadi beberapa peta dapat memberikan percepatan yang signifikan untuk bagian pemrosesan tersebut.
Stephen C
sumber
30

Cukup gunakan ConcurrentHashMap. ConcurrentHashMap menggunakan beberapa kunci yang mencakup berbagai keranjang hash untuk mengurangi kemungkinan penguncian diperebutkan. Ada dampak kinerja marjinal untuk memperoleh kunci yang tidak terbantahkan.

Untuk menjawab pertanyaan awal Anda: Menurut javadoc, selama struktur peta tidak berubah, Anda baik-baik saja. Ini berarti tidak ada elemen yang dihapus sama sekali dan tidak ada penambahan kunci baru yang belum ada di peta. Mengganti nilai yang terkait dengan kunci yang ada tidak masalah.

Jika beberapa utas mengakses peta hash secara bersamaan, dan setidaknya salah satu utas memodifikasi peta secara struktural, peta itu harus disinkronkan secara eksternal. (Modifikasi struktural adalah setiap operasi yang menambah atau menghapus satu atau lebih pemetaan; hanya mengubah nilai yang terkait dengan kunci yang sudah berisi instance bukanlah modifikasi struktural.)

Padahal tidak ada jaminan tentang visibilitas. Jadi, Anda harus bersedia menerima pengambilan asosiasi lama sesekali.

Tim Bender
sumber
6

Itu tergantung pada apa yang Anda maksud di bawah "mengakses". Jika Anda hanya membaca, Anda dapat membaca bahkan kunci yang sama selama visibilitas data dijamin di bawah aturan " terjadi sebelum ". Ini berarti HashMaptidak boleh berubah dan semua perubahan (konstruksi awal) harus diselesaikan sebelum pembaca mulai mengakses HashMap.

Jika Anda mengubah file HashMap dengan cara apa pun maka kode Anda rusak. @Stephen C memberikan penjelasan yang sangat bagus mengapa.

EDIT: Jika kasus pertama adalah situasi Anda yang sebenarnya, saya sarankan Anda menggunakan Collections.unmodifiableMap()untuk memastikan bahwa HashMap Anda tidak pernah berubah. Objek yang dituju HashMaptidak boleh berubah juga, jadi agresif menggunakanfinal kata kunci yang dapat membantu Anda.

Dan seperti yang dikatakan @ Lars Andren, ConcurrentHashMapadalah pilihan terbaik dalam banyak kasus.

Denis Bazhenov
sumber
2
ConcurrentHashMap adalah pilihan terbaik menurut saya. Satu-satunya alasan saya tidak merekomendasikannya, karena penulis tidak memintanya :) Ini memiliki lebih sedikit throughput karena operasi CAS, tetapi sebagai aturan emas pemrograman bersamaan mengatakan: "Lakukan dengan benar, dan baru kemudian buat cepat ":)
Denis Bazhenov
unmodifiableMapmemastikan klien tidak dapat mengubah peta. Itu tidak melakukan apa pun untuk memastikan bahwa peta yang mendasarinya tidak berubah.
Pete Kirkham
Seperti yang sudah saya tunjukkan: "Objek yang ditunjukkan oleh HashMap tidak boleh berubah juga"
Denis Bazhenov
4

Memodifikasi HashMap tanpa sinkronisasi yang tepat dari dua utas dapat dengan mudah menyebabkan kondisi balapan.

  • Saat put()mengarah ke pengubahan ukuran tabel internal, ini membutuhkan waktu dan utas lainnya terus menulis ke tabel lama.
  • Dua put()untuk kunci berbeda mengarah ke update bucket yang sama jika kode hash kunci sama modulo ukuran tabel. (Sebenarnya, hubungan antara kode hash dan indeks keranjang lebih rumit, tetapi tabrakan mungkin masih terjadi.)
Christian Semrau
sumber
1
Ini lebih buruk dari sekedar kondisi balapan. Bergantung pada internal HashMapimplementasi yang Anda gunakan, Anda bisa mendapatkan kerusakan HashMapstruktur data, dan sebagainya yang disebabkan oleh anomali memori.
Stephen C