1. Introduction à l'échec rapide
"Fast Fail" est également appelé échec-fast, qui est un mécanisme de détection d'erreur pour les collections Java. Lorsqu'un thread itère sur la collection, les autres threads ne sont pas autorisés à modifier la collection structurellement.
Par exemple: Supposons qu'il y ait deux threads (filetage 1 et filetage 2) et que le thread 1 traverse les éléments de l'ensemble A traversant l'itérateur. À un moment donné, Thread 2 modifie la structure de l'ensemble A (une modification de la structure, plutôt que de simplement modifier le contenu de l'élément de jeu), puis le programme lancera une exception de laModification ConcurrentModification, générant ainsi un échec.
Le comportement de défaillance rapide de l'itérateur ne peut pas être garanti, il ne peut garantir que l'erreur se produira, de sorte que la conception concurrentModificationException doit être utilisée uniquement pour détecter les bogues.
Toutes les classes de collecte du package java.util échouent rapidement, tandis que les classes de collecte du package java.util.concurrent échouent en toute sécurité;
L'itérateur qui échoue lance rapidement une conception concurrente en conception, tandis que l'itérateur qui échoue en toute sécurité ne lance jamais cette exception.
2 exemples de faillite
Exemple de code: (fastfailtest.java)
Importer java.util. *; Importer java.util.concurrent. *; / * * @desc Test Program for Fast-Fail dans la collection Java. * * Conditions pour l'événement Fast-Fail: Lorsque plusieurs threads fonctionnent sur la collection, si l'un des threads traverse la collection via l'itérateur, le contenu de la collection est modifié par d'autres threads; Une exception en conception concurrente-motification sera lancée. * Solution rapide: Si vous le traitez via la classe correspondante dans le package UTIL.Concurrent, l'événement Fast-Fail ne sera pas généré. * * Dans cet exemple, les deux cas d'ArrayList et CopyOnwriteArrayList sont testés respectivement. ArrayList générera un événement rapide, tandis que CopyOnwriteArrayList ne générera pas un événement à talet rapide. * (01) Lorsque vous utilisez ArrayList, un événement à charge rapide sera généré et une exception ConcurrentModificationException sera lancée; La définition est la suivante: * Liste statique privée <string> list = new ArrayList <string> (); * (02) Lors de l'utilisation de CopyOnWriteArrayList, un événement à charge rapide ne sera pas généré; La définition est la suivante: * Liste statique privée <string> list = new CopyOnwriteArrayList <string> (); * * @author skywang * / classe publique fastfailTest {private static list <string> list = new ArrayList <string> (); // Liste statique privée <string> list = new CopyOnwriteArrayList <string> (); public static void main (String [] args) {// Démarrer deux threads en même temps pour fonctionner sur la liste! Nouveau threadOne (). start (); nouveau threadtwo (). start (); } private static void printall () {System.out.println (""); String Value = NULL; Iterator iter = list.iterator (); while (iter.hasnext ()) {value = (string) iter.next (); System.out.print (valeur + ","); }} / ** * Ajoutez 0,1,2,3,4,5 à la liste tour à tour. Après avoir ajouté un nombre, itérates via printall () * / classe statique privée ThreadOne étend Thread {public void run () {int i = 0; while (i <6) {list.add (string.valueof (i)); printall (); i ++; }}} / ** * Ajouter 10,11,12,13,14,15 à la liste tour à tour. Une fois chaque numéro ajouté, il traversera toute la liste via printall () * / classe statique privée threadtwo étend Thread {public void run () {int i = 10; while (i <16) {list.add (string.valueof (i)); printall (); i ++; }}}} Exécutez le code en conséquence et lance une exception java.util.concurrentModificationException! Autrement dit, un événement de faillite est généré!
Description des résultats
(01) Dans FastFailTest, démarrez deux threads en même temps pour faire fonctionner la liste via New ThreadOne (). Start () et New ThreadTwo (). Start ().
Filetage Threadone: Ajoutez 0, 1, 2, 3, 4, 5 à la liste. Une fois chaque numéro ajouté, la liste entière est traversée via printall ().
Threadtwo Thread: Ajoutez 10, 11, 12, 13, 14, 15 à la liste. Une fois chaque numéro ajouté, la liste entière est traversée via printall ().
(02) Lorsqu'un thread traverse la liste, le contenu de la liste est modifié par un autre thread; Une exception en conception concurrente-motification sera lancée, ce qui entraînera un événement rapide.
3. Solution rapide
Le mécanisme rapide est un mécanisme de détection d'erreur. Il ne peut être utilisé que pour détecter les erreurs, car JDK ne garantit pas que le mécanisme de faillite se produira. Si vous utilisez une collection de mécanismes d'échec dans un environnement multi-thread, il est recommandé d'utiliser "les classes sous java.util.concurrent" pour remplacer "des classes sous le package java.util".
Par conséquent, dans cet exemple, il vous suffit de remplacer ArrayList par la classe correspondante sous le package java.util.concurrent. C'est-à-dire le code
Liste statique privée <string> list = new ArrayList <string> ();
Remplacer par
Liste statique privée <string> list = new CopyOnWriteArrayList <string> ();
Cette solution peut être résolue.
4. Principe de faillite
L'événement FAIL-FAST est généré, qui est déclenché en lançant une exception en conception concurrentModificationException.
Alors, comment ArrayList lève-t-il une exception de concurrentModificationException?
Nous savons que ConcurrentModificationException est une exception lancée lors du fonctionnement de l'itérateur. Jetons d'abord un coup d'œil au code source de l'itérateur. L'itérateur de ArrayList est implémenté dans la classe Parent AbstractList.java. Le code est le suivant:
Package Java.util;
classe abstraite publique AbstractList <E> étend AbstractCollection <E> implémente List <e> {... // L'attribut unique dans AbstractList // utilisé pour enregistrer le nombre de listes modifiées: chaque fois (Add / Supprimer les opérations, etc.), modCount + 1 Transient Int ModCount = 0; // Renvoie l'itérateur pour la liste. En fait, il s'agit de retourner l'objet ITR. public iterator <e> iterator () {return new itr (); } // ITR est la classe d'implémentation d'Iterator (Iterator). classe privée ITR implémente Iterator <e> {int cursor = 0; int lastret = -1; // Modifiez la valeur d'enregistrement du nombre. // Chaque fois qu'un nouvel objet IRT () est créé, le modCount correspondant lorsque le nouvel objet est créé sera enregistré; // Chaque fois que vous traversez les éléments de la liste, vous comparerez si attendumodCount et ModCount sont égaux; // Si ce n'est pas égal, une exception en conception concurrente enception est lancée, ce qui entraîne un événement rapide. int attendModCount = modCount; public boolean hasnext () {return Cursor! = size (); } public e suivant () {// Avant d'obtenir l'élément suivant, il sera jugé si le "modCount enregistré lors de la création d'un nouvel objet ITR" et "ModCount actuel" est égal; // S'il n'est pas égal, une exception en conception concurrente enontant la conception est lancée, ce qui entraîne un événement fasciné. checkForComodification (); essayez {e next = get (curseur); lastret = cursor ++; retour ensuite; } catch (indexoutofboundSexception e) {checkForComodification (); lancer un nouveau nosuchementElementException (); }} public void dissovel () {if (lastret == -1) lancez new illégalStateException (); checkForComodification (); try {abstractList.this.remove (lastret); if (lastet <curseur) curseur--; lastret = -1; attendModCount = modCount; } catch (indexOutofBoundSexception e) {Throw New ConcurrentModificationException (); }} final void checkForComodification () {if (modCount! = attendModCount) New concurrentModificationException (); }} ...} À partir de cela, nous pouvons constater que CheckForComodification () est exécuté lorsque Next () et REPOP () sont appelés. Si "ModCount n'est pas égal à attendumodCount", une exception concurrentModificationException est lancée, ce qui entraîne un événement rapide.
Pour comprendre le mécanisme de faillite, nous devons comprendre quand "ModCount ne fait pas d'égalité attenduModCount"!
À partir de la classe ITR, nous savons que attendumodCount est affecté à ModCount lors de la création de l'objet ITR. Grâce à ITR, nous savons que l'attente de modCount ne peut pas être modifiée pour ne pas être égal à modCount. Par conséquent, ce qui doit être vérifié, c'est quand ModCount sera modifié.
Ensuite, vérifions le code source de ArrayList pour voir comment ModCount est modifié.
package java.util; public class ArrayList <e> étend AbstractList <e> implémente la liste <e>, RandomAccess, Clonable, java.io.serializable {... // Lorsque la capacité change dans la liste, la fonction de synchronisation correspondante publique void assurecapacity (int mincapacity) {modCount ++; int oldcapacity = elementData.length; if (mincapacity> oldcapacity) {object olddata [] = elementData; int newcapacity = (oldcapacity * 3) / 2 + 1; if (newcapacity <mincapacity) newCapacity = mincapacity; // La mincapacité est généralement proche de la taille, il s'agit donc d'une victoire: elementData = arrays.copyof (elementData, newcapacity); }} // Ajouter un élément au dernier de la file d'attente Boolean Add (E E) {// Modifier ModCount Assurecapacité (taille + 1); // incréments modCount !! elementData [size ++] = e; Retour Vrai; } // Ajouter un élément à l'emplacement spécifié public void add (int index, e élément) {if (index> size || index <0) lancez un nouvel indexoutofboundSexception ("index:" + index + ", taille:" + size); // Modifier ModCount Assurecapacité (taille + 1); // incréments modCount !! System.ArrayCopy (élémentData, index, elementData, index + 1, taille - index); elementData [index] = élément; taille ++; } // Ajouter la collection publique booléen addall (collection <? Étend e> c) {objet [] a = c.toarray (); int numNew = a.Length; // Modifier ModCount Assurecapacité (taille + numNew); // incréments modCount System.ArrayCopy (A, 0, ElementData, taille, numnew); taille + = numnew; retourner numnew! = 0; } // Supprimer l'élément dans l'emplacement spécifié public e devov (int index) {rangeCheck (index); // Modifier modCount modCount ++; E oldvalue = (e) elementData [index]; int numMoved = size - index - 1; if (numMoved> 0) System.ArrayCopy (elementData, index + 1, elementData, index, numMoved); elementData [- size] = null; // Laissez GC faire son travail retourne OldValue; } // Supprimer rapidement les éléments dans l'emplacement spécifié private void fastRemove (int index) {// modifier modCount modCount ++; int numMoved = size - index - 1; if (numMoved> 0) System.ArrayCopy (elementData, index + 1, elementData, index, numMoved); elementData [- size] = null; // Laissez GC faire son travail} // effacer la collection public void clear () {// modifier modCount modCount ++; // Soit GC faire son travail pour (int i = 0; i <taille; i ++) elementData [i] = null; taille = 0; } ...} À partir de cela, nous avons constaté que, qu'il s'agisse d'ajouter (), de retirer () ou de clear (), la valeur de modCount sera modifiée tant qu'elle implique de modifier le nombre d'éléments dans l'ensemble.
Ensuite, trier systématiquement comment l'échec est produit. Les étapes sont les suivantes:
(01) Créez un nouveau nom ArrayList en tant qu'arrayList.
(02) Ajouter du contenu à l'arrayList.
(03) Créez un nouveau "thread A" et lisez la valeur de ArrayList à plusieurs reprises via Iterator dans "Thread A".
(04) Créez un nouveau "Thread B" et supprimez un "Node A" Dans la liste Array dans "Thread B".
(05) Pour le moment, des événements intéressants se produiront.
À un moment donné, "Le fil a" crée l'itérateur de l'arraylist. À l'heure actuelle, "nœud a" existe toujours dans l'arraylist. Lors de la création d'un ArrayList, attendModCount = modCount (en supposant que leurs valeurs sont n à l'heure actuelle).
À un moment donné pendant le processus de traversée de l'arrayList, "Thread B" exécute, et "Thread B" supprime le nœud A "dans l'arrayList. Lorsque "Thread B" Exécute Suppor () pour la suppression de l'opération, "modCount ++" est exécuté dans repos () et modCount devient n + 1!
"Fixez A" puis traverse. Lorsqu'il exécute la fonction suivante (), CheckForComodification () est appelé pour comparer les tailles de "attendumodCount" et "modCount"; et "attendModCount = n", "modCount = n + 1", donc une exception concurrentModificationException est lancée, résultant en un événement rapide.
À ce stade, nous avons une compréhension complète de la façon dont l'échec est produit!
Autrement dit, lorsque plusieurs threads fonctionnent sur le même ensemble, lorsqu'un thread accède à l'ensemble, le contenu de l'ensemble est modifié par d'autres threads (c'est-à-dire que d'autres threads modifient la valeur de modCount via ADD, supprimer, claire et d'autres méthodes); À l'heure actuelle, une exception en conception concurrente-motification sera lancée, ce qui entraînera un événement rapide.
5. Le principe de résolution de l'échec rapide
Ce qui précède explique les "méthodes pour résoudre le mécanisme de faillite" et connaît également la "cause profonde du fast-fast". Ensuite, parlons davantage de la façon de résoudre l'événement de faillite dans le package java.util.concurrent.
Expliquons-le avec le CopyOnwriteArrayList correspondant à ArrayList. Jetons d'abord un coup d'œil au code source de CopyOnWriteArrayList:
package java.util.concurrent;import java.util.*;import java.util.concurrent.locks.*;import sun.misc.Unsafe;public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ... // Return the iterator corresponding to the collection public Iterator<E> iterator() {Renvoie les méthodes de mise en œuvre de Fast-Fail dans la nouvelle classe de collection sont presque les mêmes. Prenons l'exemple ArrayList le plus simple. Int transitoire protégé ModCount = 0; Enregistre le nombre de fois que nous modifions ArrayList. Par exemple, lorsque nous appelons ADD (), supprimer (), etc. Pour modifier les données, ModCount ++ sera modifié. Int transitoire protégé ModCount = 0; Enregistre le nombre de fois que nous modifions ArrayList. Par exemple, lorsque nous appelons ADD (), supprimer (), etc. Pour modifier les données, ModCount ++ sera modifié. Cowiterator <e> (getArray (), 0); } ... Cowiterator de classe statique privée <E> implémente ListIterator <E> {objet final privé [] instantanée; curseur int privé; Cowiterator privé (objet [] Elements, int initialCursor) {cursor = initialCursor; // Lors de la création d'un nouveau co-co-co-co-cowerator, enregistrez les éléments de la collection dans un nouveau tableau de copie. // De cette façon, lorsque les données de l'ensemble d'origine changent, les valeurs des données de copie ne changeront pas non plus. instantané = éléments; } public boolean hasnext () {return cursor <snapshot.length; } public boolean hasprevious () {return curseur> 0; } public e next () {if (! Hasnext ()) lance un nouveau nosuchementElementException (); return (e) instantané [curseur ++]; } public e précédemment () {if (! Hasprevious ()) lance un nouveau nosuchementElementException (); retour (e) instantané [- curseur]; } public int nextIndex () {return Cursor; } public int promedIndex () {return Cursor-1; } public void dissovel () {lance un nouveau non support-operationxception (); } public void set (e e) {lancez un nouveau non soutenu par une conception (); } public void add (e e) {lancez un nouveau non soutenues d'OperationException (); }} ...} De cela, nous pouvons voir:
(01) Contrairement à ArrayList hérite de AbstractList, CopyOnwriteArrayList n'hérédit pas de AbstractList, il implémente uniquement l'interface de liste.
(02) L'itérateur renvoyé par la fonction Iterator () d'ArrayList est implémenté dans AbstractList; tandis que CopyOnwriteArrayList implémente l'itérateur lui-même.
(03) Lorsque Next () est appelé dans la classe d'implémentation Iterator d'ArrayList, "Call CheckForComodification () pour comparer les tailles de 'attendModCount' et 'ModCount'"; Cependant, il n'y a pas de soi-disant CheckForComodification () dans la classe d'implémentation Iterator de CopyOrNWriteArrayList, et la conception ConcurrentModificationException ne sera pas lancée!
6. Résumé
Étant donné que Hashmap (ArrayList) n'est pas un thread-safe, si d'autres threads modifient la carte pendant le processus d'utilisation de l'itérateur (la modification ici fait référence à la modification structurelle, et non simplement pour modifier les éléments du contenu de la collection), alors une conception concurrente-exception sera lancée, c'est-à-dire la stratégie de fast-fast est principalement mise en œuvre à travers le champ moderne pour assurer la visibilité entre les threads. ModCount est le nombre de modifications. Cette valeur sera ajoutée à la modification du contenu HashMap (ArrayList). Ensuite, lors de l'initialisation de l'itérateur, cette valeur sera attribuée à l'attention de l'itérateur.
Mais le comportement rapide n'est pas garanti, donc la pratique de s'appuyer sur cette exception est erronée