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.