1. Pengantar gagal-cepat
"Fast Fail" juga disebut gagal-cepat, yang merupakan mekanisme deteksi kesalahan untuk koleksi Java. Ketika utas berulang di atas koleksi, utas lain tidak diizinkan untuk memodifikasi koleksi secara struktural.
Misalnya: Misalkan ada dua utas (utas 1 dan utas 2), dan utas 1 melintasi elemen -elemen dalam set A melalui iterator. Pada titik tertentu, Thread 2 memodifikasi struktur set A (modifikasi pada struktur, daripada hanya memodifikasi konten elemen yang ditetapkan), maka program akan melempar pengecualian Exception Exception ConcurrentModification, sehingga menghasilkan gagal-gagal.
Perilaku kegagalan cepat iterator tidak dapat dijamin, tidak dapat menjamin bahwa kesalahan akan terjadi, sehingga ConcurrentModificationException harus digunakan hanya untuk mendeteksi bug.
Semua kelas koleksi dalam paket java.util gagal dengan cepat, sedangkan kelas koleksi di paket java.util.concurrent gagal dengan aman;
Iterator yang gagal dengan cepat melempar ConcurrentModificationException, sedangkan iterator yang gagal dengan aman tidak pernah melempar pengecualian ini.
2 contoh gagal-cepat
Kode contoh: (fastfailtest.java)
Impor java.util.*; impor java.util.concurrent.*;/** @desc Program uji untuk Fast-Fail di Java Collection. * * Kondisi untuk acara cepat-cepat: Ketika beberapa utas beroperasi pada koleksi, jika salah satu utas melintasi koleksi melalui iterator, konten koleksi diubah oleh utas lain; Pengecualian ConcurrentModification Exception akan dilemparkan. * Solusi cepat-cepat: Jika Anda memprosesnya melalui kelas yang sesuai di bawah paket koleksi Util.CONCURRENT, acara cepat-cepat tidak akan dihasilkan. * * Dalam contoh ini, dua kasus ArrayList dan CopyOnWriteArrayList diuji masing -masing. ArrayList akan menghasilkan acara yang gagal cepat, sementara CopyOnWriteArrayList tidak akan menghasilkan acara cepat gagal. * (01) Saat menggunakan ArrayList, acara cepat-cepat akan dihasilkan dan pengecualian Exception ConcurrentModification akan dilemparkan; Definisi ini adalah sebagai berikut: * Daftar statis pribadi <string> Daftar = ArrayList baru <String> (); * (02) Saat menggunakan CopyonWriteArrayList, acara cepat gagal tidak akan dihasilkan; Definisi ini adalah sebagai berikut: * Private Static List <String> Daftar = new copyonWriteArrayList <string> (); * * @Author Skywang */kelas publik FastFailTest {Private Static List <String> Daftar = ArrayList baru <String> (); // Daftar Statis Privat <String> Daftar = CopyOnWriteArrayList baru <string> (); public static void main (string [] args) {// Mulai dua utas secara bersamaan untuk beroperasi di daftar! threadOne baru (). start (); threadTwo baru (). start (); } private static void printAll () {System.out.println (""); Nilai string = null; Iterator iter = list.iterator (); while (iter.hasnext ()) {value = (string) iter.next (); System.out.print (nilai+","); }} /*** Tambahkan 0,1,2,3,4,5 ke daftar pada gilirannya. Setelah menambahkan angka, iterasi melalui printAll () */ private static class ThreadOne memperluas thread {public void run () {int i = 0; while (i <6) {list.add (string.valueof (i)); printall (); i ++; }}} /*** Tambahkan 10,11,12,13,14,15 ke daftar pada gilirannya. Setelah setiap nomor ditambahkan, itu akan melintasi seluruh daftar melalui printAll () */ Private Static Class ThreadTwo Extends Thread {public void run () {int i = 10; while (i <16) {list.add (string.valueof (i)); printall (); i ++; }}}} Jalankan kode sebagai hasilnya dan melempar pengecualian java.util.concurrentModificationException! Artinya, peristiwa gagal-cepat dihasilkan!
Deskripsi Hasil
(01) Di FastFailTest, mulai dua utas secara bersamaan untuk mengoperasikan daftar melalui threadOne baru (). Mulai () dan threadTwo baru (). Start ().
ThreadOne Thread: Tambahkan 0, 1, 2, 3, 4, 5 ke daftar. Setelah setiap nomor ditambahkan, seluruh daftar dilalui melalui printall ().
ThreadTwo Thread: Tambahkan 10, 11, 12, 13, 14, 15 ke daftar. Setelah setiap nomor ditambahkan, seluruh daftar dilalui melalui printall ().
(02) Ketika utas melintasi daftar, konten daftar diubah oleh utas lain; Pengecualian Exception ConcurrentModification akan dilemparkan, menghasilkan peristiwa gagal-cepat.
3. Solusi gagal-cepat
Mekanisme gagal-cepat adalah mekanisme deteksi kesalahan. Ini hanya dapat digunakan untuk mendeteksi kesalahan, karena JDK tidak menjamin bahwa mekanisme gagal-cepat akan terjadi. Jika Anda menggunakan kumpulan mekanisme gagal-cepat di lingkungan multi-utas, disarankan untuk menggunakan "kelas di bawah java.util.concurrent Package" untuk menggantikan "kelas di bawah paket java.util".
Oleh karena itu, dalam contoh ini, Anda hanya perlu mengganti daftar array dengan kelas yang sesuai di bawah paket java.util.concurrent. Yaitu kodenya
Private Static List <String> Daftar = ArrayList baru <string> ();
Ganti dengan
Private Static List <String> list = new copyOnWriteArrayList <string> ();
Solusi ini dapat diselesaikan.
4. Prinsip gagal-cepat
Peristiwa gagal-cepat dihasilkan, yang dipicu dengan melempar pengecualian Exception ConcurrentModification.
Jadi, bagaimana ArrayList melempar pengecualian Exception ConcurrentModification?
Kita tahu bahwa ConcurrentModificationException adalah pengecualian yang dilemparkan saat mengoperasikan iterator. Mari kita lihat kode sumber iterator terlebih dahulu. Iterator ArrayList diimplementasikan dalam kelas induk abstraklist.java. Kodenya adalah sebagai berikut:
paket java.util;
Kelas Abstrak Publik AbstractList <E> Memperluas Abstrak AbstractCollection <E> Menerapkan Daftar <E> {... // Atribut unik dalam AbstractList // digunakan untuk merekam jumlah daftar yang dimodifikasi: setiap kali (Tambah/Hapus Operasi, dll.), MODCOUNT+1 INT MODCOUNT transien yang dilindungi = 0; // Kembalikan Iterator untuk Daftar. Bahkan, itu adalah mengembalikan objek ITR. Iterator publik <E> iterator () {return new iTr (); } // ITR adalah kelas implementasi iterator (iterator). kelas pribadi ITR mengimplementasikan iterator <E> {int kursor = 0; int lastret = -1; // Ubah nilai catatan angka. // Setiap kali objek ITR () baru dibuat, modcount yang sesuai ketika objek baru dibuat akan disimpan; // Setiap kali Anda melintasi elemen dalam daftar, Anda akan membandingkan apakah diharapkan MODCount dan ModCount sama; // Jika tidak sama, pengecualian ConcurrentModificationException dilemparkan, menghasilkan peristiwa gagal-cepat. int diharapkanmodcount = modcount; public boolean hasnext () {return cursor! = size (); } public e next () {// Sebelum mendapatkan elemen berikutnya, itu akan dinilai apakah "modcount disimpan saat membuat objek ITR baru" dan "MODCount saat ini" sama; // Jika tidak sama, pengecualian Exception ConcurrentModification dilemparkan, menghasilkan peristiwa gagal-cepat. checkForComodification (); coba {e next = get (kursor); lastret = kursor ++; kembali berikutnya; } catch (indexOutofboundsException e) {checkForComodification (); Lempar NosuchelementException baru (); }} public void remeFe () {if (lastret == -1) Lempar baru ilegalstateException (); checkForComodification (); coba {abstractlist.this.remove (lastret); if (lastret <kursor) kursor--; lastret = -1; diharapkan modcount = modcount; } catch (IndexOutOfBoundsException e) {Throw New ConcurrentModificationException (); }} final void checkForComodification () {if (modcount! = diharapkan modcount) lempar concurrentModificationException baru (); }} ...} Dari ini, kita dapat menemukan bahwa checkForComodification () dieksekusi saat berikutnya () dan hapus () dipanggil. Jika "ModCount tidak sama dengan yang diharapkan MODCount", pengecualian ConcurrentModificationException dilemparkan, menghasilkan peristiwa gagal-cepat.
Untuk memahami mekanisme gagal-cepat, kita perlu memahami ketika "ModCount tidak sama dengan yang diharapkan dari MODCount"!
Dari kelas ITR, kita tahu bahwa diharapkan MODCount ditugaskan ke MODCount saat membuat objek ITR. Melalui ITR, kita tahu bahwa MODCount yang diharapkan tidak dapat dimodifikasi menjadi tidak sama dengan modcount. Oleh karena itu, apa yang perlu diverifikasi adalah ketika MODCount akan dimodifikasi.
Selanjutnya, mari kita periksa kode sumber ArrayList untuk melihat bagaimana modcount dimodifikasi.
Paket java.util; arraylist kelas publik <e> memperluas daftar abstrak <e> mengimplementasikan daftar <e>, acak, dapat dikloning, java.io.serializable {... // Ketika kapasitas berubah dalam daftar, fungsi sinkronisasi yang sesuai public void assurecapacity (int mincapacity) {Modcount +++; int oldcapacity = elementData.length; if (Mincapacity> oldcapacity) {objek oldData [] = elementData; int newcapacity = (oldcapacity * 3)/2 + 1; if (newcapacity <mincapacity) newcapacity = mincapacity; // MinCapacity biasanya mendekati ukuran, jadi ini adalah win: elementData = arrays.copyof (elementData, newcapacity); }} // Tambahkan elemen ke yang terakhir dari antrian public boolean add (e e) {// modifikasi modcount EnsureCapacity (size + 1); // Menambah ModCount !! elementData [size ++] = e; Kembali Benar; } // Tambahkan elemen ke lokasi yang ditentukan public void add (indeks int, elemen e) {if (index> size || index <0) lempar indexOutofboundsException baru ("index:"+index+", size:"+size); // Ubah ModCount Ensurecapacity (ukuran+1); // Menambah ModCount !! System.ArrayCopy (ElementData, Indeks, ElementData, Indeks + 1, Ukuran - Indeks); elementData [index] = elemen; ukuran ++; } // Tambahkan koleksi public boolean addall (koleksi <? Extends e> c) {objek [] a = c.toArray (); int numNew = a.length; // Ubah ModCount EnsureCapacity (ukuran + numNew); // meningkatkan modcount system.arraycopy (a, 0, elementData, size, numnew); ukuran += numnew; kembalikan numnew! = 0; } // hapus elemen di lokasi yang ditentukan publik e hapus (indeks int) {rangecheck (index); // Ubah ModCount ModCount ++; E oldValue = (e) elementData [indeks]; int nummoved = ukuran - indeks - 1; if (nummoved> 0) system.arraycopy (elementData, index+1, elementData, index, nummoved); elementData [-size] = null; // Biarkan GC melakukan pekerjaannya mengembalikan OldValue; } // Hapus elemen dengan cepat di lokasi yang ditentukan private void fastremove (int index) {// Modifikasi modcount modcount ++; int nummoved = ukuran - indeks - 1; if (nummoved> 0) system.arraycopy (elementData, index+1, elementData, index, nummoved); elementData [-size] = null; // Biarkan GC melakukan pekerjaannya} // Hapus koleksi public void clear () {// Modifikasi modcount modcount ++; // Biarkan GC melakukan pekerjaannya untuk (int i = 0; i <size; i ++) elementData [i] = null; ukuran = 0; } ...} Dari ini, kami menemukan bahwa apakah itu add (), rapE (), atau clear (), nilai modcount akan diubah selama itu melibatkan memodifikasi jumlah elemen dalam set.
Selanjutnya, mari kita selesaikan secara sistematis bagaimana gagal-cepat dihasilkan. Langkah -langkahnya adalah sebagai berikut:
(01) Buat nama arraylist baru sebagai arraylist.
(02) Tambahkan konten ke ArrayList.
(03) Buat "Thread A" baru dan baca nilai ArrayList berulang kali melalui Iterator di "Thread A".
(04) Buat "Thread B" baru dan hapus A "Node A" di ArrayList di "Thread B".
(05) Saat ini, peristiwa menarik akan terjadi.
Pada titik tertentu, "Thread a" menciptakan iterator arraylist. Pada saat ini, "Node A" masih ada di daftar array. Saat membuat Daftar Array, PrecipedModCount = ModCount (dengan asumsi bahwa nilainya adalah N saat ini).
Pada titik tertentu selama proses melintasi arraylist, "Thread B" dieksekusi, dan "Thread B" menghapus "simpul A" di daftar array. Ketika "Thread B" mengeksekusi lepas () untuk operasi hapus, "ModCount ++" dieksekusi di Remove (), dan ModCount menjadi N+1!
"Thread A" Lalu melintasi. Ketika mengeksekusi fungsi berikutnya (), checkForComodification () dipanggil untuk membandingkan ukuran "diharapkan Modcount" dan "modcount"; dan "diharapkan modcount = n", "modcount = n+1", sehingga pengecualian Exception ConcurrentModification dilemparkan, menghasilkan peristiwa gagal-cepat.
Pada titik ini, kami memiliki pemahaman lengkap tentang bagaimana gagal-cepat dihasilkan!
Yaitu, ketika beberapa utas beroperasi pada set yang sama, ketika utas mengakses set, konten set diubah oleh utas lain (yaitu, utas lain mengubah nilai MODCount melalui Tambah, Hapus, Bersihkan dan Metode Lainnya); Pada saat ini, pengecualian Exception ConcurrentModification akan dilemparkan, menghasilkan peristiwa gagal-cepat.
5. Prinsip pemecahan gagal gagal
Di atas menjelaskan "metode untuk menyelesaikan mekanisme gagal-cepat" dan juga mengetahui "akar penyebab kegagalan-cepat". Selanjutnya, mari kita bicara lebih lanjut tentang cara menyelesaikan acara gagal-cepat dalam paket java.util.concurrent.
Mari kita jelaskan dengan CopyonWriteArrayList yang sesuai dengan ArrayList. Pertama -tama mari kita lihat kode sumber CopyOnWriteArrayList:
Paket java.util.concurrent; impor java.util.*; impor java.util.concurrent.locks.*; impor sun.misc.unsafe; public copyononWriteArrayList <e> Daftar <e>, acak, itu dapat dikloning, java.ializable {... iterator () {mengembalikan metode implementasi cepat-cepat di kelas koleksi baru hampir sama. Mari kita ambil daftar array paling sederhana sebagai contoh. transien intrect yang dilindungi modcount = 0; mencatat berapa kali kami memodifikasi arraylist. Misalnya, ketika kami memanggil ADD (), lepas (), dll. Untuk mengubah data, MODCount ++ akan diubah. transien intrect yang dilindungi modcount = 0; mencatat berapa kali kami memodifikasi arraylist. Misalnya, ketika kami memanggil ADD (), lepas (), dll. Untuk mengubah data, MODCount ++ akan diubah. Cowiterator <E> (getArray (), 0); } ... cowiterator kelas statis privat <E> mengimplementasikan ListIterator <E> {Private Final Object [] Snapshot; kursor int pribadi; Private Cowiterator (Object [] Elements, int InitialCursor) {cursor = initialCursor; // Saat membuat cowiterator baru, simpan elemen dalam koleksi ke array salinan baru. // Dengan cara ini, ketika data set asli berubah, nilai dalam data salin tidak akan berubah. snapshot = elemen; } public boolean hasnext () {return cursor <snapshot.length; } public boolean hasprevious () {return kursor> 0; } public e next () {if (! hasnext ()) lempar nosuchelementException baru (); return (e) snapshot [kursor ++]; } public e sebelumnya () {if (! hasprevious ()) melempar nosuchelementException baru (); return (e) snapshot [-kursor]; } public int nextIndex () {return kursor; } public int priorIndex () {return cursor-1; } public void remove () {throw new UnsupportedOperationException (); } public void set (e e) {lempar baru unsportedOperationException (); } public void add (e e) {lempar baru unsportedOperationException (); }} ...} Dari sini kita dapat melihat:
(01) Tidak seperti ArrayList mewarisi dari AbstractList, CopyonWriteArrayList tidak mewarisi dari AbstractList, itu hanya mengimplementasikan antarmuka daftar.
(02) Iterator yang dikembalikan oleh fungsi iterator () dari ArrayList diimplementasikan dalam AbstractList; sementara CopyOnWriteArrayList mengimplementasikan iterator itu sendiri.
(03) ketika selanjutnya () dipanggil di kelas implementasi iterator ArrayList, "call checkForComodification () untuk membandingkan ukuran 'diharapkanmodcount' dan 'modcount'"; Namun, tidak ada apa yang disebut checkForComodification () di kelas implementasi Iterator dari CopyonWriteArrayList, dan ConcurrentModificationException tidak akan dilemparkan!
6. Ringkasan
Karena HashMap (ArrayList) tidak aman, jika utas lain memodifikasi peta selama proses penggunaan iterator (modifikasi di sini mengacu pada modifikasi struktural, bukan hanya untuk memodifikasi elemen dari konten pengumpulan), maka concurrentModificationException akan dilemparkan, yaitu, strategi gagal yang gagal. Terutama yang diimplementasikan melalui modifikasi Modcount akan dilemparkan, yaitu, strategi gagal-cepat terutama diimplementasikan. ModCount adalah jumlah modifikasi. Nilai ini akan ditambahkan ke modifikasi konten HashMap (ArrayList). Kemudian selama inisialisasi iterator, nilai ini akan ditugaskan ke Iterator yang diharapkan.
Tetapi perilaku gagal-cepat tidak dijamin, jadi praktik mengandalkan pengecualian ini adalah salah