Dans JDK Collection, nous voyons souvent des mots similaires à celui-ci :
Par exemple, ArrayList :
注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽 最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭 代器的快速失败行为应该仅用于检测 bug。
HashMap :
注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大 努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应 该仅用于检测程序错误。
mentionné à plusieurs reprises « échouer rapidement » dans ces deux paragraphes. Alors, quel est le mécanisme du « fail fast » ?
"Fast Failure" est fail-fast, qui est un mécanisme de détection d'erreurs pour les collections Java. Lorsque plusieurs threads effectuent des modifications structurelles dans une collection, un mécanisme d'échec rapide peut se produire. N'oubliez pas que c'est possible, pas certain. Par exemple : supposons qu'il y ait deux threads (Thread 1, Thread 2). Le thread 1 parcourt les éléments de l'ensemble A via Iterator. À un moment donné, le thread 2 modifie la structure de l'ensemble A (c'est une modification de la structure, pas). une simple modification du contenu de l'élément de collection), le programme lancera alors une exception ConcurrentModificationException à ce moment-là, générant ainsi un mécanisme d'échec rapide.
1. Exemple d'échec rapide
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(); } }
Résultat d'exécution :
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. 🎜>Grâce aux exemples et explications ci-dessus, je sais au départ que la raison de l'échec rapide est que lorsque le programme itère la collection, un thread apporte des modifications structurelles à la collection. À ce moment, les informations d'exception de l'itérateur ConcurrentModificationException seront. être lancé, ce qui entraîne un échec rapide.
Pour comprendre le mécanisme de défaillance rapide, nous devons d'abord comprendre l'exception ConcurrentModificationException. Cette exception est levée lorsqu'une méthode détecte une modification simultanée d'un objet mais n'autorise pas une telle modification. Dans le même temps, il convient de noter que cette exception n'indiquera pas toujours que l'objet a été modifié simultanément par différents threads. Si un seul thread viole les règles, il peut également lever une exception.
Il est vrai que le comportement fail-fast des itérateurs n'est pas garanti, et cela ne garantit pas que cette erreur se produira, mais l'opération fail-fast fera de son mieux pour lever une exception ConcurrentModificationException, donc donc , afin d'améliorer l'exactitude de ces opérations. C'est une erreur d'écrire un programme qui s'appuie sur cette exception. L'approche correcte est la suivante : ConcurrentModificationException ne doit être utilisée que pour détecter les bogues. Ci-dessous, j'utiliserai ArrayList comme exemple pour analyser plus en détail les raisons de l'échec rapide.
D'après le précédent, nous savons que le fail-fast est généré lors de l'utilisation des itérateurs. Jetons maintenant un coup d'œil au code source de l'itérateur dans 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(); } }
À partir du code source ci-dessus, nous pouvons voir que l'itérateur appelle toujours la méthode checkForComodification() lors de l'appel de next() et remove( ), cette méthode sert principalement à détecter modCount == ExpectModCount? Sinon, une exception ConcurrentModificationException sera levée, générant ainsi un mécanisme d'échec rapide. Donc, pour comprendre pourquoi le mécanisme de défaillance rapide se produit, nous devons comprendre pourquoi modCount != ExpectModCount et quand leurs valeurs ont changé.
expectedModCount est défini dans Itr : int ExpectModCount = ArrayList.this.modCount ; sa valeur ne peut donc pas être modifiée, donc ce qui va changer est modCount. modCount est défini dans AbstractList et est une variable globale :
protected transient int modCount = 0;
Alors, quand change-t-il et pour quelle raison ? Veuillez regarder le code source d'ArrayList :
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 /** 省略此处代码 */ }
À partir du code source ci-dessus, nous pouvons voir que quelles que soient les méthodes add, delete ou clear dans ArrayList, tant qu'elles impliquent de modifier le nombre d'éléments ArrayList , modCount changera. Nous pouvons donc ici juger à titre préliminaire que la valeur attendue de ModCount et le changement de modCount sont désynchronisés, ce qui entraîne une inégalité entre les deux, entraînant un mécanisme de défaillance rapide. Connaissant la cause première du fail-fast, nous pouvons avoir le scénario suivant :
Il y a deux threads (thread A, thread B), parmi lesquels le thread A est responsable de parcourir la liste et le thread B modifie la liste . À un moment donné, pendant que le thread A parcourt la liste (à ce moment attenduModCount = modCount=N), le thread démarre et en même temps le thread B ajoute un élément, ce qui signifie que la valeur de modCount change (modCount + 1 = N + 1). Lorsque le thread A continue de parcourir et d'exécuter la méthode suivante, il notifie à la méthode checkForComodification que ExpectModCount = N et modCount = N + 1, qui ne sont pas égaux. À ce moment, une exception ConcurrentModificationException est levée, entraînant un échec. -mécanisme rapide.
Ainsi, jusqu’à présent, nous avons pleinement compris la cause profonde de l’échec rapide. Une fois que vous connaissez la raison, il est plus facile de trouver une solution.
3. Solution anti-échecGrâce aux exemples précédents et à l'analyse du code source, je pense que vous avez essentiellement compris le mécanisme d'échec rapide. Ci-dessous, je vais générer des raisons. et des solutions. Il existe deux solutions ici :
Solution 1 : pendant le processus de traversée, ajoutez synchronisé à tous les endroits qui impliquent de modifier la valeur modCount ou utilisez directement Collections.synchronizedList, ce qui peut résoudre le problème. Cependant, cela n'est pas recommandé car les verrous de synchronisation provoqués par des ajouts et des suppressions peuvent bloquer les opérations de parcours.
Option 2 : utilisez CopyOnWriteArrayList pour remplacer ArrayList. Cette solution est recommandée.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!