Mengapa ConcurrentModificationException dilemparkan dan cara debug itu

130

Saya menggunakan Collection(yang HashMapdigunakan secara tidak langsung oleh JPA, itu terjadi), tetapi ternyata secara acak kode tersebut melempar a ConcurrentModificationException. Apa yang menyebabkannya dan bagaimana cara memperbaiki masalah ini? Dengan menggunakan sinkronisasi, mungkin?

Ini adalah stack-trace lengkap:

Exception in thread "pool-1-thread-1" java.util.ConcurrentModificationException
        at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
        at java.util.HashMap$ValueIterator.next(Unknown Source)
        at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:555)
        at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
        at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
        at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
        at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
        at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
mainstringargs
sumber
1
Bisakah Anda memberikan lebih banyak konteks? Apakah Anda menggabungkan, memperbarui, atau menghapus entitas? Asosiasi apa yang dimiliki entitas ini? Bagaimana dengan pengaturan cascading Anda?
ordnungswidrig
1
Dari jejak tumpukan Anda dapat melihat bahwa Pengecualian terjadi saat iterasi melalui HashMap. Tentunya beberapa utas lainnya memodifikasi peta tetapi pengecualian terjadi pada utas yang iterasi.
Chochos

Jawaban:

263

Ini bukan masalah sinkronisasi. Ini akan terjadi jika koleksi dasar yang sedang diiterasi diubah oleh apa pun selain Iterator itu sendiri.

Iterator it = map.entrySet().iterator();
while (it.hasNext())
{
   Entry item = it.next();
   map.remove(item.getKey());
}

Ini akan melempar ConcurrentModificationExceptionketika it.hasNext()disebut kedua kalinya.

Pendekatan yang tepat adalah

   Iterator it = map.entrySet().iterator();
   while (it.hasNext())
   {
      Entry item = it.next();
      it.remove();
   }

Dengan asumsi iterator ini mendukung remove()operasi.

Robin
sumber
1
Mungkin, tetapi sepertinya Hibernate melakukan iterasi, yang harus diimplementasikan dengan benar. Mungkin ada panggilan balik yang memodifikasi peta, tapi itu tidak mungkin. Ketidakpastian menunjuk ke masalah konkurensi aktual.
Tom Hawtin - tackline
Pengecualian ini tidak ada hubungannya dengan threading concurrency, hal ini disebabkan oleh backing store dari iterator yang sedang dimodifikasi. Apakah dengan utas yang lain tidak penting bagi iterator. IMHO, ini adalah pengecualian dengan nama buruk karena memberikan kesan yang salah tentang penyebabnya.
Robin
Namun saya setuju bahwa jika tidak dapat diprediksi, ada kemungkinan besar masalah threading yang menyebabkan kondisi untuk pengecualian ini terjadi. Yang membuatnya semakin membingungkan karena nama pengecualian.
Robin
Ini benar dan penjelasan yang lebih baik daripada jawaban yang diterima, tetapi jawaban yang diterima adalah perbaikan yang bagus. ConcurrentHashMap tidak dikenakan CME, bahkan di dalam iterator (meskipun iterator masih dirancang untuk akses single-thread).
G__
Solusi ini tidak ada gunanya, karena Maps tidak memiliki metode iterator (). Contoh Robin akan berlaku untuk misalnya Daftar.
peter
72

Coba gunakan ConcurrentHashMapbukan dataranHashMap

Chochos
sumber
Apakah itu benar-benar menyelesaikan masalah? Saya mengalami masalah yang sama tetapi saya pasti bisa mengesampingkan masalah threading.
tobiasbayer
5
Solusi lain adalah membuat salinan peta dan beralih melalui salinan itu. Atau salin set kunci dan beralih melalui mereka, dapatkan nilai untuk setiap kunci dari peta asli.
Chochos
Hibernate-lah yang melakukan iterasi melalui koleksi sehingga Anda tidak bisa begitu saja menyalinnya.
tobiasbayer
1
Penyelamat instan. Pergi ke mencari tahu mengapa ini bekerja dengan sangat baik sehingga saya tidak mendapatkan lebih banyak kejutan di ujung jalan.
Valchris
1
Saya kira itu bukan masalah sinkronisasi itu masalah jika modifikasi modifikasi yang sama saat perulangan objek yang sama.
Rais Alam
17

Modifikasi dari Collectionsementara iterasi melalui itu Collectionmenggunakan sebuah Iteratoryang tidak diizinkan oleh sebagian besar Collectionkelas. Perpustakaan Java menyebut upaya untuk memodifikasi Collectionsementara iterasi melalui itu sebagai "modifikasi bersamaan". Sayangnya, satu-satunya penyebab yang mungkin adalah modifikasi simultan oleh banyak utas, tetapi tidak demikian. Dengan hanya menggunakan satu utas dimungkinkan untuk membuat iterator untuk Collection(menggunakan Collection.iterator(), atau loop yang disempurnakanfor ), mulai iterasi (menggunakan Iterator.next(), atau secara setara memasuki tubuh forloop yang disempurnakan ), memodifikasi Collection, lalu melanjutkan iterasi.

Untuk membantu programmer, beberapa implementasi dari Collectionkelas - kelas itu berusaha mendeteksi modifikasi bersamaan yang salah, dan melempar ConcurrentModificationExceptionjika mereka mendeteksinya. Namun, secara umum tidak mungkin dan praktis untuk menjamin deteksi semua modifikasi bersamaan. Jadi penggunaan yang salah Collectiontidak selalu menghasilkan terlempar ConcurrentModificationException.

Dokumentasi ConcurrentModificationExceptionmengatakan:

Pengecualian ini dapat dilemparkan oleh metode yang telah mendeteksi modifikasi bersamaan dari suatu objek ketika modifikasi tersebut tidak diizinkan ...

Perhatikan bahwa pengecualian ini tidak selalu menunjukkan bahwa suatu objek telah diubah secara bersamaan oleh utas yang berbeda. Jika satu utas mengeluarkan urutan pemanggilan metode yang melanggar kontrak suatu objek, objek tersebut dapat membuang pengecualian ini ...

Perhatikan bahwa perilaku gagal-cepat tidak dapat dijamin karena, secara umum, tidak mungkin untuk membuat jaminan keras di hadapan modifikasi bersamaan yang tidak disinkronkan. Operasi gagal-cepat ConcurrentModificationExceptionberdasarkan upaya terbaik.

Catat itu

Dokumentasi HashSet, HashMap, TreeSetdan ArrayListkelas kata ini:

Iterator yang dikembalikan [langsung atau tidak langsung dari kelas ini] gagal-cepat: jika [koleksi] dimodifikasi kapan saja setelah iterator dibuat, dengan cara apa pun kecuali melalui metode hapus iterator sendiri, Iteratorlemparan a ConcurrentModificationException. Dengan demikian, dalam menghadapi modifikasi bersamaan, iterator gagal dengan cepat dan bersih, daripada mengambil risiko perilaku non-deterministik yang sewenang-wenang pada waktu yang tidak ditentukan di masa depan.

Perhatikan bahwa perilaku gagal-cepat dari iterator tidak dapat dijamin karena, secara umum, tidak mungkin untuk membuat jaminan keras di hadapan modifikasi bersamaan yang tidak disinkronkan. Iterator gagal-cepat melemparkan ConcurrentModificationExceptionpada upaya terbaik. Oleh karena itu, akan salah untuk menulis sebuah program yang bergantung pada pengecualian ini untuk kebenarannya: perilaku iterator yang gagal cepat harus digunakan hanya untuk mendeteksi bug .

Perhatikan lagi bahwa perilaku "tidak dapat dijamin" dan hanya "berdasarkan upaya terbaik".

Dokumentasi beberapa metode Mapantarmuka mengatakan ini:

Implementasi non-konkuren harus menimpa metode ini dan, berdasarkan upaya terbaik, melempar ConcurrentModificationExceptionjika terdeteksi bahwa fungsi pemetaan memodifikasi peta ini selama perhitungan. Implementasi bersamaan harus mengesampingkan metode ini dan, atas dasar upaya terbaik, melempar IllegalStateExceptionjika terdeteksi bahwa fungsi pemetaan memodifikasi peta ini selama perhitungan dan akibatnya perhitungan tidak akan pernah selesai.

Perhatikan lagi bahwa hanya "upaya terbaik" yang diperlukan untuk deteksi, dan a ConcurrentModificationExceptionsecara eksplisit disarankan hanya untuk kelas yang tidak berbarengan (tidak aman untuk benang).

Debugging ConcurrentModificationException

Jadi, ketika Anda melihat tumpukan-jejak karena ConcurrentModificationException, Anda tidak dapat langsung berasumsi bahwa penyebabnya adalah akses multi-utas yang tidak aman ke a Collection. Anda harus memeriksa stack-trace untuk menentukan kelas Collectionmelempar pengecualian (metode kelas akan secara langsung atau tidak langsung melemparkannya), dan untuk Collectionobjek mana . Maka Anda harus memeriksa dari mana objek itu dapat dimodifikasi.

  • Penyebab paling umum adalah modifikasi Collectiondalam forloop yang ditingkatkan di atas Collection. Hanya karena Anda tidak melihat Iteratorobjek di kode sumber Anda tidak berarti tidak ada di Iteratorsana! Untungnya, salah satu pernyataan dari forloop yang salah biasanya akan ada di tumpukan-jejak, jadi melacak kesalahan biasanya mudah.
  • Kasus rumit adalah ketika kode Anda berkeliling referensi ke Collectionobjek. Perhatikan bahwa pandangan koleksi yang tidak dapat dimodifikasi (seperti yang diproduksi oleh Collections.unmodifiableList()) mempertahankan referensi ke koleksi yang dapat dimodifikasi, sehingga iterasi atas koleksi yang "tidak dapat dimodifikasi" dapat membuang pengecualian (modifikasi telah dilakukan di tempat lain). Tampilan lain dari Anda Collection, seperti sub daftar , Mapset entri dan Mapset kunci juga mempertahankan referensi ke yang asli (dapat dimodifikasi) Collection. Ini bisa menjadi masalah bahkan untuk thread-safe Collection, seperti CopyOnWriteList; jangan berasumsi bahwa koleksi thread-safe (konkuren) tidak pernah dapat membuang pengecualian.
  • Operasi mana yang dapat dimodifikasi Collectiondapat terjadi secara tak terduga dalam beberapa kasus. Misalnya, LinkedHashMap.get()memodifikasi koleksinya .
  • Kasus-kasus yang paling sulit adalah ketika pengecualian adalah karena modifikasi bersamaan oleh beberapa thread.

Pemrograman untuk mencegah kesalahan modifikasi bersamaan

Jika memungkinkan, batasi semua referensi ke Collectionobjek, jadi lebih mudah untuk mencegah modifikasi bersamaan. Membuat Collectionsebuah privateobjek atau variabel lokal, dan tidak kembali referensi ke Collectionatau iterator yang dari metode. Maka jauh lebih mudah untuk memeriksa semua tempat di mana Collectiondapat dimodifikasi. Jika Collectionakan digunakan oleh banyak utas, maka praktis untuk memastikan bahwa utas mengakses Collectionhanya dengan sinkronisasi dan penguncian yang sesuai.

Raedwald
sumber
Saya bertanya-tanya mengapa modifikasi bersamaan tidak diperbolehkan jika ada satu utas. Masalah apa yang dapat terjadi jika satu utas diizinkan untuk melakukan modifikasi bersamaan pada peta hash biasa?
MasterJoe
4

Di Java 8, Anda bisa menggunakan ekspresi lambda:

map.keySet().removeIf(key -> key condition);
Zentopia
sumber
2

Itu terdengar kurang seperti masalah sinkronisasi Java dan lebih seperti masalah penguncian basis data.

Saya tidak tahu apakah menambahkan versi ke semua kelas gigih Anda akan mengatasinya, tapi itu salah satu cara Hibernate dapat memberikan akses eksklusif ke baris dalam tabel.

Bisa jadi level isolasi perlu lebih tinggi. Jika Anda mengizinkan "pembacaan kotor", mungkin Anda perlu menambahkan serializable.

Duffymo
sumber
Saya pikir mereka berarti Hashtable. Itu dikirim sebagai bagian dari JDK 1.0. Seperti Vector, ini ditulis agar aman - dan lambat. Keduanya telah digantikan oleh alternatif aman non-utas: HashMap dan ArrayList. Bayar untuk apa yang Anda gunakan.
duffymo
0

Cobalah CopyOnWriteArrayList atau CopyOnWriteArraySet tergantung pada apa yang Anda coba lakukan.

Javamann
sumber
0

Perhatikan bahwa jawaban yang dipilih tidak dapat diterapkan ke konteks Anda secara langsung sebelum beberapa modifikasi, jika Anda mencoba untuk menghapus beberapa entri dari peta saat iterasi peta seperti saya.

Saya hanya memberikan contoh kerja saya di sini untuk pemula untuk menghemat waktu mereka:

HashMap<Character,Integer> map=new HashMap();
//adding some entries to the map
...
int threshold;
//initialize the threshold
...
Iterator it=map.entrySet().iterator();
while(it.hasNext()){
    Map.Entry<Character,Integer> item=(Map.Entry<Character,Integer>)it.next();
    //it.remove() will delete the item from the map
    if((Integer)item.getValue()<threshold){
        it.remove();
    }
ZhaoGang
sumber
0

Saya mengalami pengecualian ini ketika mencoba menghapus x item terakhir dari daftar. myList.subList(lastIndex, myList.size()).clear();adalah satu-satunya solusi yang berhasil untuk saya.

orang abu-abu
sumber