In der JDK-Sammlung sehen wir oft Wörter wie diese:
Zum Beispiel ArrayList:
注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽 最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭 代器的快速失败行为应该仅用于检测 bug。
HashMap:
注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大 努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应 该仅用于检测程序错误。
In diesen beiden Absätzen wurde wiederholt von „fail fast“ gesprochen. Was ist also der „Fail-Fast“-Mechanismus?
„Fast Failure“ ist Fail-Fast, ein Fehlererkennungsmechanismus für Java-Sammlungen. Wenn mehrere Threads strukturelle Änderungen an einer Sammlung vornehmen, kann ein Fail-Fast-Mechanismus auftreten. Denken Sie daran, es ist möglich, aber nicht sicher. Beispiel: Angenommen, es gibt zwei Threads (Thread 1, Thread 2). Irgendwann ändert Thread 2 die Struktur von Set A (es handelt sich um eine Modifikation der Struktur). ein einfaches Ändern des Inhalts des Sammlungselements), dann löst das Programm zu diesem Zeitpunkt eine ConcurrentModificationException-Ausnahme aus und generiert so einen Fail-Fast-Mechanismus.
1. Beispiel für Fail-Fast
public class FailFastTest { private static List<Integer> list = new ArrayList<>(); /** * @desc:线程one迭代list * @Project:test * @file:FailFastTest.java * @Authro:chenssy * @data:2014年7月26日 */ private static class threadOne extends Thread{ public void run() { Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ int i = iterator.next(); System.out.println("ThreadOne 遍历:" + i); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * @desc:当i == 3时,修改list * @Project:test * @file:FailFastTest.java * @Authro:chenssy * @data:2014年7月26日 */ private static class threadTwo extends Thread{ public void run(){ int i = 0 ; while(i < 6){ System.out.println("ThreadTwo run:" + i); if(i == 3){ list.remove(i); } i++; } } } public static void main(String[] args) { for(int i = 0 ; i < 10;i++){ list.add(i); } new threadOne().start(); new threadTwo().start(); } }
Laufergebnis:
ThreadOne 遍历:0 ThreadTwo run:0 ThreadTwo run:1 ThreadTwo run:2 ThreadTwo run:3 ThreadTwo run:4 ThreadTwo run:5 Exception in thread "Thread-0" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at java.util.ArrayList$Itr.next(Unknown Source) at test.ArrayListTest$threadOne.run(ArrayListTest.java:23
2. Ursache für Fail-Fast
Durch die obigen Beispiele und Erklärungen weiß ich zunächst, dass der Grund für Fail-Fast darin liegt, dass ein Thread beim Iterieren der Sammlung strukturelle Änderungen an der Sammlung vornimmt. Zu diesem Zeitpunkt werden die Ausnahmeinformationen des Iterators ConcurrentModificationException angezeigt geworfen werden, was zu einem Fail-Fast führt.
Um den Fail-Fast-Mechanismus zu verstehen, müssen wir zunächst die ConcurrentModificationException-Ausnahme verstehen. Diese Ausnahme wird ausgelöst, wenn eine Methode gleichzeitige Änderungen an einem Objekt erkennt, solche Änderungen jedoch nicht zulässt. Gleichzeitig ist zu beachten, dass diese Ausnahme nicht immer darauf hinweist, dass das Objekt gleichzeitig von verschiedenen Threads geändert wurde. Wenn ein einzelner Thread gegen die Regeln verstößt, kann es auch zu einer Ausnahme kommen.
Es ist wahr, dass das Fail-Fast-Verhalten von Iteratoren nicht garantiert ist und auch nicht garantiert, dass dieser Fehler auftritt, aber der Fail-Fast-Vorgang wird sein Bestes tun, um eine ConcurrentModificationException-Ausnahme auszulösen Um die Korrektheit solcher Operationen zu verbessern, wäre es ein Fehler, ein Programm zu schreiben, das auf dieser Ausnahme basiert: ConcurrentModificationException sollte nur zum Erkennen von Fehlern verwendet werden. Im Folgenden werde ich ArrayList als Beispiel verwenden, um die Gründe für Fail-Fast weiter zu analysieren.
Aus dem Vorhergehenden wissen wir, dass beim Betrieb von Iteratoren ein Fail-Fast generiert wird. Werfen wir nun einen Blick auf den Quellcode des Iterators in ArrayList:
private class Itr implements Iterator<E> { int cursor; int lastRet = -1; int expectedModCount = ArrayList.this.modCount; public boolean hasNext() { return (this.cursor != ArrayList.this.size); } public E next() { checkForComodification(); /** 省略此处代码 */ } public void remove() { if (this.lastRet < 0) throw new IllegalStateException(); checkForComodification(); /** 省略此处代码 */ } final void checkForComodification() { if (ArrayList.this.modCount == this.expectedModCount) return; throw new ConcurrentModificationException(); } }
Aus dem obigen Quellcode können wir sehen, dass der Iterator immer die Methode checkForComodification() aufruft, wenn er next() und remove( aufruft. )-Methoden dienen hauptsächlich dazu, modCount == erwartetModCount? zu erkennen. Wenn nicht, wird eine ConcurrentModificationException-Ausnahme ausgelöst, wodurch ein Fail-Fast-Mechanismus generiert wird. Um also herauszufinden, warum der Fail-Fast-Mechanismus auftritt, müssen wir herausfinden, warum modCount != erwartetModCount und wann sich ihre Werte geändert haben.
expectedModCount ist in Itr definiert: int erwartetModCount = ArrayList.this.modCount; daher kann sein Wert nicht geändert werden, was sich also ändern wird, ist modCount. modCount ist in AbstractList definiert und ist eine globale Variable:
protected transient int modCount = 0;
Wann ändert es sich also und aus welchem Grund? Schauen Sie sich bitte den Quellcode von ArrayList an:
public boolean add(E paramE) { ensureCapacityInternal(this.size + 1); /** 省略此处代码 */ } private void ensureCapacityInternal(int paramInt) { if (this.elementData == EMPTY_ELEMENTDATA) paramInt = Math.max(10, paramInt); ensureExplicitCapacity(paramInt); } private void ensureExplicitCapacity(int paramInt) { this.modCount += 1; //修改modCount /** 省略此处代码 */ } public boolean remove(Object paramObject) { int i; if (paramObject == null) for (i = 0; i < this.size; ++i) { if (this.elementData[i] != null) continue; fastRemove(i); return true; } else for (i = 0; i < this.size; ++i) { if (!(paramObject.equals(this.elementData[i]))) continue; fastRemove(i); return true; } return false; } private void fastRemove(int paramInt) { this.modCount += 1; //修改modCount /** 省略此处代码 */ } public void clear() { this.modCount += 1; //修改modCount /** 省略此处代码 */ }
Aus dem obigen Quellcode können wir ersehen, dass dies unabhängig von den Add-, Remove- oder Clear-Methoden in ArrayList der Fall ist, sofern sie eine Änderung der Anzahl der ArrayList-Elemente beinhalten , modCount ändert sich. Hier können wir also vorläufig beurteilen, dass der erwartete ModCount-Wert und die Änderung von modCount nicht synchron sind, was zu einer Ungleichheit zwischen den beiden führt und zu einem Fail-Fast-Mechanismus führt. Wenn wir die Grundursache für Fail-Fast kennen, können wir das folgende Szenario haben:
Es gibt zwei Threads (Thread A, Thread B), von denen Thread A für das Durchlaufen der Liste verantwortlich ist und Thread B die Liste ändert . Irgendwann, während Thread A die Liste durchläuft (zu diesem Zeitpunkt wird ModCount = modCount = N erwartet), startet der Thread und gleichzeitig fügt Thread B ein Element hinzu, was bedeutet, dass sich der Wert von modCount ändert (modCount + 1 = N +). 1). Wenn Thread A die nächste Methode weiter durchläuft und ausführt, benachrichtigt er die checkForComodification-Methode darüber, dass „expectedModCount = N“ und „modCount = N + 1“ nicht gleich sind. Zu diesem Zeitpunkt wird eine ConcurrentModificationException-Ausnahme ausgelöst, was zu einem Fehler führt -schneller Mechanismus.
Bis zu diesem Punkt haben wir also die Grundursache für Fail-Fast vollständig verstanden. Sobald Sie den Grund kennen, ist es einfacher, eine Lösung zu finden.
3. Fail-Fast-Lösung
Durch die vorherigen Beispiele und die Quellcode-Analyse denke ich, dass Sie den Fail-Fast-Mechanismus grundsätzlich verstanden haben und Lösungen. Hier gibt es zwei Lösungen:
Lösung 1: Fügen Sie während des Durchlaufvorgangs synchronisiert an allen Stellen hinzu, an denen der modCount-Wert geändert werden muss, oder verwenden Sie Collections.synchronizedList direkt, um das Problem zu lösen. Dies wird jedoch nicht empfohlen, da durch Hinzufügungen und Löschungen verursachte Synchronisierungssperren Durchlaufvorgänge blockieren können.
Option 2: Verwenden Sie CopyOnWriteArrayList, um ArrayList zu ersetzen. Diese Lösung wird empfohlen.
Das obige ist der detaillierte Inhalt vonFail-Fast-Mechanismus. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!