1. Einführung in den Fail-Fast
"Fast Fail" wird auch als Fail-Fast bezeichnet, was ein Fehlererkennungsmechanismus für Java-Sammlungen ist. Wenn ein Thread über die Sammlung iteriert, dürfen andere Threads die Sammlung nicht strukturell ändern.
Zum Beispiel: Angenommen, es gibt zwei Threads (Thread 1 und Faden 2), und Thread 1 durchquert die Elemente in Set a durch Iterator. Irgendwann modifiziert Thread 2 die Struktur von Set a (eine Modifikation in der Struktur, anstatt einfach den Inhalt des Satzelements zu ändern), und dann wird das Programm eine Ausnahme von ConcurrentModificificationException ausgelöst, wodurch ein fehlender Schnitt generiert wird.
Das schnelle Fehlerverhalten des Iterators kann nicht garantiert werden, es kann nicht garantieren, dass der Fehler auftritt, sodass die ConcurrentModificationException nur zum Erkennen von Fehler verwendet werden sollte.
Alle Sammlungskurse im Java.util -Paket scheitern schnell, während die Sammelklassen im Java.util.Concurrent -Paket sicher ausfallen.
Der Iterator, der fehlschlägt, wirft schnell eine ConcurrentModificificationException aus, während der Iterator, der diese Ausnahme sicher fehlschlägt, nie sicher macht.
2 Beispiele für fehlgeschlagene Beispiele
Beispielcode: (fastFailTest.java)
Java.util importieren.*; Import Java.util.Concurrent. * * Bedingungen für das Fast-Fail-Ereignis: Wenn mehrere Threads in der Sammlung arbeiten, wird der Inhalt der Sammlung durch einen der Threads durch die Sammlung durch den Iterator durch andere Threads geändert. Eine Ausnahme von ConcurrentModificationException wird ausgelöst. * Fast-Fail-Lösung: Wenn Sie sie durch die entsprechende Klasse unter dem Paket für das Sammlung von Util.Concurrent verarbeiten, wird das Fast-Fail-Ereignis nicht generiert. * * In diesem Beispiel werden die beiden Fälle von ArrayList und CopyonWriteArrayList jeweils getestet. ArrayList generiert ein Fast-Fail-Ereignis, während CopyonWriteArrayList kein Fast-Fail-Ereignis generiert. * (01) Bei der Verwendung von ArrayList wird ein Fast-Fail-Ereignis generiert und eine Ausnahme von ConcurrentModificationException ausgelöst. Die Definition lautet wie folgt: * private statische Liste <string> list = new ArrayList <string> (); * (02) Bei der Verwendung von CopyonWriteArrayList wird ein Fast-Fail-Ereignis nicht generiert. Die Definition lautet wie folgt: * private statische Liste <string> list = newOnWriteArrayList <string> (); * * @Author Skywang */public class fastFailTest {private statische Liste <string> list = new ArrayList <string> (); // private statische Liste <string> list = newOnWriteArrayList <string> (); public static void main (String [] args) {// zwei Threads gleichzeitig starten, um auf der Liste zu arbeiten! neuer ThreadOne (). start (); neuer ThreadTWO (). start (); } private statische void printall () {System.out.println (""); String value = null; Iterator iter = list.Iterator (); while (iter.hasnext ()) {value = (string) iter.next (); System.out.print (Wert+","); }} /*** 0,1,2,3,4,5 in die Liste hinzufügen. Nach dem Hinzufügen einer Zahl iteriert Printall () */ private statische Klasse ThreadOne Thread {public void run () {int i = 0; while (i <6) {list.add (string.Valueof (i)); printall (); i ++; }}} /*** Fügen Sie der Liste der Liste 10,11,12,13,14,15 hinzu. Nachdem jede Zahl hinzugefügt wurde, durchquert sie die gesamte Liste über printall () */ private statische Klasse ThreadTwo erweitert Thread {public void run () {int i = 10; while (i <16) {list.add (string.Valueof (i)); printall (); i ++; }}}} Führen Sie den Code als Ergebnis aus und geben Sie eine Ausnahme aus Java.util.ConcurrentModificationException! Das heißt, ein fehlgeschnelles Ereignis wird generiert!
Ergebnisse Beschreibung
(01) Starten Sie in FastFailTest zwei Threads gleichzeitig, um die Liste über New ThreadOne () zu bedienen. Start () und neuer ThreadTWO (). Start ().
ThreadOne -Thread: Fügen Sie der Liste 0, 1, 2, 3, 4, 5 hinzu. Nachdem jede Zahl hinzugefügt wurde, wird die gesamte Liste durch printall () durchquert.
ThreadTWO -Thread: Fügen Sie der Liste 10, 11, 12, 13, 14, 15 hinzu. Nachdem jede Zahl hinzugefügt wurde, wird die gesamte Liste durch printall () durchquert.
(02) Wenn ein Thread die Liste durchquert, wird der Inhalt der Liste durch einen anderen Thread geändert. Eine Ausnahme von ConcurrentModificationException wird ausgelöst, was zu einem fehlgeschlagenen Ereignis führt.
3. Fail-Fast-Lösung
Der fehlgeschnittene Mechanismus ist ein Fehlererkennungsmechanismus. Es kann nur verwendet werden, um Fehler zu erkennen, da JDK nicht garantiert, dass der fehlgeschlagene Mechanismus auftritt. Wenn Sie eine Sammlung von fehlgeschnittenen Mechanismus in einer Umgebung mit mehreren Threads verwenden, wird empfohlen, "Klassen unter java.util.Concurrent-Paket" zu verwenden, um "Klassen unter java.util-Paket" zu ersetzen.
In diesem Beispiel müssen Sie daher nur die ArrayList durch die entsprechende Klasse unter dem Paket java.util.concurrent ersetzen. Das heißt, der Code
private statische Liste <string> list = new ArrayList <string> ();
Ersetzen durch
private statische Liste <string> list = newOnonwriteArrayList <string> ();
Diese Lösung kann gelöst werden.
V
Das fehlgeschnittene Ereignis wird erzeugt, das durch Ausnahme einer ConcurrentModificationException-Ausnahme ausgelöst wird.
Wie wirft ArrayList eine Ausnahme von ConcurrentModificationException aus?
Wir wissen, dass die ConcurrentModificationException eine Ausnahme ist, die beim Betrieb von Iterator ausgelöst wird. Schauen wir uns zuerst den Iterator -Quellcode an. Der Iterator von ArrayList wird in der Parent Class AbstractList.java implementiert. Der Code ist wie folgt:
Paket java.util;
public abstract class AbstractList <E> erweitert AbstractCollection <E> implementiert die Liste <e> {... // Das eindeutige Attribut in AbstractList // verwendet, um die Anzahl der geänderten Liste zu erfassen: Jedes Mal (Hinzufügen/Löschen von Operationen usw.), ModCount+1 Protected Transient int modcount = 0; // Geben Sie den Iterator für die Liste zurück. Tatsächlich soll das ITR -Objekt zurückgegeben werden. public iterator <e> iterator () {return New iTR (); } // ITR ist die Implementierungsklasse von Iterator (Iterator). Private Klasse ITR implementiert Iterator <E> {int Cursor = 0; int lastret = -1; // Ändern Sie den Datensatzwert der Nummer. // Jedes Mal, wenn ein neues ITR () -Objekt erstellt wird, wird das entsprechende Modcount, wenn das neue Objekt erstellt wird, gespeichert. // Jedes Mal, wenn Sie die Elemente in der Liste durchqueren, vergleichen Sie, ob erweitertModcount und modcount gleich sind. // Wenn nicht gleich, wird eine Ausnahme von ConcurrentModificificationException ausgelöst, was zu einem fehlgeschlagenen Ereignis führt. int erwartModcount = modcount; public boolean hasNext () {return cursor! = size (); } public e next () {// Bevor das nächste Element erhalten wird, wird beurteilt, ob das "ModCount beim Erstellen eines neuen ITR -Objekts" und "aktueller ModCount" gleich sind. // Wenn es nicht gleich ist, wird eine Ausnahme von ConcurrentModificationException ausgelöst, was zu einem fehlgeschlagenen Ereignis führt. checkforComodification (); Versuchen Sie {e next = get (cursor); Lastret = Cursor ++; Als nächstes zurückkehren; } catch (indexoutOfBoundSexception e) {checkforComodification (); neue NoSuchelementException () werfen; }} public void remove () {if (lastret == -1) werfen neue illegaleStateException (); checkforComodification (); try {AbstractList.this.Remove (Lastret); if (lastret <cursor) cursor--; Lastret = -1; erweitertModcount = modcount; } catch (indexoutOfboundSexception e) {neue ConcurrentModificationException (); }} endgültig void checkforComodification () {if (modcount! = erwartungsModcount) werfen neue ConcurrentModificationException (); }} ...} Aus diesem Grund können wir feststellen, dass CheckforComodification () ausgeführt wird, wenn Next () und REME () aufgerufen werden. Wenn "ModCount nicht gleich dem erwartetenModcount" ist, wird eine Ausnahme von ConcurrentModificationException ausgelöst, was zu einem fehlgeschlagenen Ereignis führt.
Um den fehlgeschnittenen Mechanismus zu verstehen, müssen wir verstehen, wann "modcount nicht gleich erwartetModcount" ist!
Aus der ITR -Klasse wissen wir, dass Modcount beim Erstellen des ITR -Objekts erweitertModcount zugeordnet ist. Durch ITR wissen wir, dass erweitertModcount nicht so modifiziert werden kann, dass sie nicht gleich modcount sind. Daher muss überprüft werden, wenn ModCount geändert wird.
Lassen Sie uns als nächstes den Quellcode von ArrayList überprüfen, um zu sehen, wie ModCount geändert wird.
Paket java.util; public class ArrayList <E> erweitert die AbstractList <E> implementiert die Liste <E>, randomaccess, klonbar, java.io.serializable {... // Wenn sich die Kapazität ändert, ändert sich die entsprechende Synchronisierungsfunktion öffentlicher Hohlraum -SeealEcapacity (int Mincapacity) {modcount ++; int oldCapacity = elementData.length; if (mincapacity> OldCapacity) {Object OldData [] = elementData; int newcapacity = (OldCapacity * 3)/2 + 1; if (newcapacity <mincapacity) newcapacity = mincapacity; // Hackigkeit liegt normalerweise nahe an der Größe, daher ist dies ein Gewinn: elementData = arrays.copyof (elementData, Newcapacity); }} // Element zum letzten der Warteschlangenpublikum boolean hinzufügen (e) {// modCount sealecapacity (Größe + 1) ändern; // Inkrementiert modcount !! ElementData [Größe ++] = e; zurückkehren; } // Element zum angegebenen Standort public void add (int Index, E -Element) {if (Index> Größe || Index <0) Wurf neu indexoutOfBoundSexception ("Index:"+index+", Größe:"+Größe); // ModCount -Sicherung modifizieren (Größe+1); // Inkrementiert modcount !! System.ArrayCopy (ElementData, Index, ElementData, Index + 1, Größe - Index); elementData [index] = element; Größe ++; } // Collection public boolean addall hinzufügen (Sammlung <? Erweitert E> c) {Object [] a = c.toarray (); int numnew = A.Length; // ModCount -Sicherung modifizieren (Größe + Numnew); // Incrents modCount System.ArrayCopy (a, 0, elementData, Größe, Numnew); Größe += numnew; return numnew! = 0; } // das Element im angegebenen Ort öffentlich e entfernen (int index) {rangecheck (index); // ModCount ModCount ++ ändern; E oldValue = (e) elementData [index]; int nummoved = Größe - Index - 1; if (nummoved> 0) system ElementData [-Größe] = NULL; // GC seine Arbeit erledigen, gibt OldValue zurück; } // Elemente schnell löschen im angegebenen Ort privat void fastremove (int index) {// ModCount modCount ++ modifizieren; int nummoved = Größe - Index - 1; if (nummoved> 0) system ElementData [-Größe] = NULL; // GC seine Arbeit erledigen} // Die Sammlung public void clear () {// modCount modCount ++ modifizieren; // GC seine Arbeit für (int i = 0; i <size; i ++) elementData [i] = null; Größe = 0; } ...} Aus diesem Grund stellten wir fest, dass der Wert von ModCount, ob es add (), remove () oder clear () ist, geändert wird, solange die Anzahl der Elemente im Satz geändert wird.
Lassen Sie uns als nächstes systematisch herausfinden, wie fehlgeschlagen wird. Die Schritte sind wie folgt:
(01) Erstellen Sie einen Namen der NeuarrayList als ArrayList.
(02) Fügen Sie der ArrayList Inhalt hinzu.
(03) Erstellen Sie einen neuen "Thread A" und lesen Sie den Wert der ArrayList wiederholt über Iterator in "Thread a".
(04) Erstellen Sie ein neues "Thread B" und löschen Sie einen "Knoten A" in der ArrayList in "Thread B".
(05) Zu diesem Zeitpunkt treten interessante Ereignisse auf.
Irgendwann erstellt "Thread A" den Iterator der ArrayList. Zu diesem Zeitpunkt gibt es in der ArrayList noch "Knoten A". Beim Erstellen einer ArrayList, erweitertModcount = modcount (unter der Annahme, dass ihre Werte zu diesem Zeitpunkt N sind).
Irgendwann während des Überfahrens der ArrayList -Thread B "wird der Thread B ausgeführt und" Thread B "löscht" Knoten A "in der ArrayList. Wenn "Thread B" für den Betrieb von REME () ausgeführt wird, wird "ModCount ++" in REMED () ausgeführt, und ModCount wird N+1!
"Thread a" dann durchquert. Wenn es die nächste () -Funktion ausführt, wird checkForComodification () aufgerufen, um die Größen von "erweitertModcount" und "modcount" zu vergleichen. und "erwartungsmodcount = n", "modcount = n+1", daher wird eine ConcurrentModificationException-Ausnahme ausgeworfen, was zu einem fehlgeschlagenen Ereignis führt.
Zu diesem Zeitpunkt haben wir ein vollständiges Verständnis dafür, wie fehlgeschlagen wird!
Das heißt, wenn mehrere Threads auf demselben Satz arbeiten, wird der Inhalt des Satzes durch andere Threads geändert (dh andere Threads ändern den Wert von ModCount durch Add-, Entfernen-, Lösch- und andere Methoden). Zu diesem Zeitpunkt wird eine Ausnahme von ConcurrentModificationException ausgelöst, was zu einem fehlgeschlagenen Ereignis führt.
5. Das Prinzip der Lösung fehlgeschlagener Lösung
Das obige erklärt die "Methoden zur Lösung des fehlgeschnittenen Mechanismus" und kennt auch die "Grundursache für den Versagenspeicher". Lassen Sie uns als nächstes weiter darüber sprechen, wie Sie das fehlgefällige Ereignis im Paket von Java.util.Concurrent lösen können.
Lassen Sie es uns mit der CopyonWriteArrayList erklären, die der ArrayList entspricht. Schauen wir uns zunächst den Quellcode von CopyonWriteArrayList an:
Paket java.util.concurrent; import Java.util.*; import Java.util.concurrent.locks. {Rückgabe Die Fast-Fail-Implementierungsmethoden in der neuen Sammlungsklasse sind fast gleich. Nehmen wir die einfachste ArrayList als Beispiel. geschützter transient int modcount = 0; Notiert, wie oft wir die ArrayList ändern. Wenn wir beispielsweise add (), remove () usw. aufrufen, um Daten zu ändern, wird ModCount ++ geändert. geschützter transient int modcount = 0; Notiert, wie oft wir die ArrayList ändern. Wenn wir beispielsweise add (), remove () usw. aufrufen, um Daten zu ändern, wird ModCount ++ geändert. Cowiterator <E> (getarray (), 0); } ... private statische Klasse COWiterator <E> implementiert Listiterator <E> {private endgültige Objekt [] Snapshot; Privat int Cursor; private Cowiterator (Objekt [] Elemente, int initialCursor) {cursor = initialCursor; // Speichern Sie bei der Erstellung eines neuen Grobitorators die Elemente in der Sammlung in einem neuen Kopierarray. // Auf diese Weise ändert sich auch die Daten des Originalsatzes in den Kopierdaten nicht. Snapshot = Elemente; } public boolean hasNext () {return cursor <snapshot.length; } public boolean hasprevious () {return cursor> 0; } public e next () {if (! hasNext ()) Wirf eine neue NoSuchelementException (); return (e) snapshot [Cursor ++]; } public e vorher () {if (! return (e) snapshot [-Cursor]; } public int nextIndex () {return cursor; } public int PotherIndex () {return cursor-1; } public void remove () {neue nicht unterstützte OperationException (); } public void set (e e) {werfen neu nicht unterstützte OperationException (); } public void add (e e) {neue nicht unterstützte OperationException (); }} ...} Daraus können wir sehen:
(01) Im Gegensatz zu ArrayList -Erben von AbstractList wird CopyonWriteArrayList nicht von AbstractList erben, sondern nur die Listenschnittstelle implementiert.
(02) Der von der iterator () -Funktion von ArrayList zurückgegebene Iterator wird in AbstractList implementiert. Während CopyonWriteArrayList den Iterator selbst implementiert.
. In der Iterator-Implementierungsklasse von CopyonWriteArrayList gibt es jedoch keine sogenannte CheckforComodification (), und die ConcurrentModificationException wird nicht geworfen!
6. Zusammenfassung
Da HashMap (ArrayList) kein Thread-Safe ist, wenn andere Threads die Karte während der Verwendung des Iterators ändern (die Änderung hier auf strukturelle Modifikation bezieht, nicht nur um Elemente des Sammelinhalts zu ändern), wird die Fehlschlagsstrategie, die durch das Modcount-Feld geworfen wird, um das Modcount-Feld zu gewährleisten, um sich zu gewährleisten. ModCount ist die Anzahl der Modifikationen. Dieser Wert wird zur Änderung des Inhalts von HashMap (ArrayList) hinzugefügt. Während der Initialisierung des Iterators wird dieser Wert dem Iterator erweitertModcount zugeordnet.
Das fehlgedichtete Verhalten ist jedoch nicht garantiert, daher ist die Praxis, sich auf diese Ausnahme zu verlassen