1. Introdução ao Fail-Fast
"Fast Fail" também é chamado de Fail-Fast, que é um mecanismo de detecção de erro para coleções Java. Quando um thread itera sobre a coleção, outros threads não podem modificar a coleção estruturalmente.
Por exemplo: suponha que haja dois threads (Thread 1 e Thread 2) e o encadeamento 1 atravessa os elementos no conjunto A através do iterador. Em algum momento, o Thread 2 modifica a estrutura do conjunto A (uma modificação na estrutura, em vez de simplesmente modificar o conteúdo do elemento definido), o programa lançará uma exceção concorrente de exceção do Fail-Fast.
O comportamento de falha rápida do iterador não pode ser garantida, não pode garantir que o erro ocorrerá, de modo que o ConcurrentModificationException deve ser usado apenas para detectar erros.
Todas as classes de coleta no pacote java.util falham rapidamente, enquanto as classes de coleta no pacote java.util.concurrent falham com segurança;
O iterador que falha rapidamente lança uma concorrência de modificação, enquanto o iterador que falha com segurança nunca lança essa exceção.
2 exemplos rápidos de falha
Código de exemplo: (fastfailtest.java)
importar java.util.*; importar java.util.Concurrent.*;/** @DESC Programa de teste para falha rápida na coleção Java. * * Condições para o evento de falha rápida: quando vários threads operam na coleção, se um dos threads percorrer a coleção através do iterador, o conteúdo da coleção é alterado por outros threads; Uma exceção concorrente de exceção de exceção será lançada. * Solução de falha rápida: se você a processar através da classe correspondente no pacote de coleta Util.Concurrent, o evento de falha rápida não será gerada. * * Neste exemplo, os dois casos de Arraylist e CopyWriteArrayList são testados, respectivamente. A ArrayList gerará um evento de falha rápida, enquanto o copywritearraylist não gerará um evento de falha rápida. * (01) Ao usar o ArrayList, um evento de falha rápida será gerada e uma exceção concorrente de exceção da UMModificação será lançada; A definição é a seguinte: * Lista estática privada <String> list = new ArrayList <String> (); * (02) Ao usar copywriteArrayList, um evento de falha rápido não será gerada; A definição é a seguinte: * Lista estática privada <String> List = new CopyOnWriteArrayList <String> (); * * @Author Skywang */public class FastFailTest {Private Static List <String> List = new ArrayList <String> (); // Lista estática privada <String> list = new copyWriteArrayList <String> (); public static void main (string [] args) {// inicie dois threads ao mesmo tempo para operar na lista! new ThreadOne (). Start (); novo 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 (valor+","); }} /*** Adicione 0,1,2,3,4,5 à lista por sua vez. Depois de adicionar um número, itera através do Printall () */ classe estática privada ThreadOne estende thread {public void run () {int i = 0; while (i <6) {list.add (string.valueof (i)); printall (); i ++; }}} /*** Adicione 10,11,12,13,14,15 à lista por sua vez. Depois que cada número for adicionado, ele atravessará toda a lista através do Printall () */ classe estática privada ThreadTwo estende thread {public void run () {int i = 10; while (i <16) {list.add (string.valueof (i)); printall (); i ++; }}}} Execute o código como resultado e lança uma exceção java.util.concurrentmodificationException! Ou seja, um evento de falha é gerado!
Resultados Descrição
(01) No FastFailTest, inicie dois threads ao mesmo tempo para operar a lista através do novo ThreadOne (). Start () e New ThreadTwo (). Start ().
ThreadOne Thread: Adicione 0, 1, 2, 3, 4, 5 à lista. Depois que cada número é adicionado, a lista inteira é percorrida através do Printall ().
ThreadTwo Thread: Adicione 10, 11, 12, 13, 14, 15 à lista. Depois que cada número é adicionado, a lista inteira é percorrida através do Printall ().
(02) Quando um thread atravessa a lista, o conteúdo da lista é alterado por outro thread; Uma exceção concorrente de exceção de exceção será lançada, resultando em um evento Fail-Fast.
3. Solução Fail-Fast
O mecanismo de falha é um mecanismo de detecção de erro. Ele só pode ser usado para detectar erros, porque o JDK não garante que o mecanismo de fracasso aconteça. Se você usar uma coleção de mecanismo de falha em um ambiente multithread, é recomendável usar "classes no pacote java.util.concurrent" para substituir "classes no pacote java.util".
Portanto, neste exemplo, você só precisa substituir o Arraylist pela classe correspondente no pacote java.util.Concurrent. Isto é, o código
Lista estática privada <String> list = new ArrayList <String> ();
Substitua por
Lista estática privada <String> list = new CopyonWriteArrayList <String> ();
Esta solução pode ser resolvida.
4. Princípio-Fast-Fast
O evento Fail-Fast é gerado, que é desencadeado lançando uma exceção concurrentmodificationException.
Então, como o ArrayList lança uma exceção concurrentmodificationException?
Sabemos que o ConcurrentModificationException é uma exceção lançada ao operar o iterador. Vamos dar uma olhada no código -fonte do iterador primeiro. O iterador do ArrayList é implementado no pai da classe dos pais abstractList.java. O código é o seguinte:
pacote java.util;
Classe abstrata public abstractList <e> estende a lista de implementos abstractCollection <E> <E> {... // O atributo exclusivo no abstrataList // usado para gravar o número de lista modificada: todas as vezes (adicione/excluir operações, etc.), modCount+1 transitório protegido int modCount = 0; // retorna o iterador para a lista. De fato, é devolver o objeto ITR. public iterator <e> iterator () {return new itr (); } // ITR é a classe de implementação do iterador (iterador). classe privada ITR implementa o iterador <E> {int cursor = 0; int lastret = -1; // modifica o valor de registro do número. // Toda vez que um novo objeto iTR () é criado, o modCount correspondente quando o novo objeto for criado será salvo; // Toda vez que você atravessa os elementos da lista, comparará se o ModCount e o ModCount esperados são iguais; // Se não for igual, uma exceção concurrentModificationException é lançada, resultando em um evento de falha. int esperamodCount = modCount; public boolean hasNext () {return cursor! = size (); } public e next () {// Antes de obter o próximo elemento, será julgado se o "ModCount salvo ao criar um novo objeto ITR" e "Modcount atual" são iguais; // Se não for igual, é lançada uma exceção concorrente de exceção da UMModificação, resultando em um evento Fail-Fast. checkForComodification (); tente {e seguinte = get (cursor); lastret = cursor ++; retornar a seguir; } catch (indexOutOfBoundSexception e) {checkForComodification (); lançar novos nosuchElementException (); }} public void remover () {if (lastret == -1) lança new ilegalStateException (); checkForComodification (); tente {abstractList.this.remove (lastret); if (lastret <cursor) cursor--; lastret = -1; esperadomodCount = modCount; } catch (indexOutOfBoundSexception e) {tiro novo concurrentModificationException (); }} Final void checkForComodification () {if (modCount! = esperadoModCount) lança novo concurrentmodificationException (); }} ...} A partir disso, podemos descobrir que o checkForComodification () é executado quando o próximo () e remover () são chamados. Se "ModCount não for igual ao esperado ModCount", uma exceção concorrente de exceção da exceção será lançada, resultando em um evento de falha.
Para entender o mecanismo de fracasso, precisamos entender quando "ModCount não é igual a ModCount esperado"!
A partir da classe ITR, sabemos que o ModCount esperado é atribuído ao ModCount ao criar o objeto ITR. Através do ITR, sabemos que o ModCount esperado não pode ser modificado para não ser igual a ModCount. Portanto, o que precisa ser verificado é quando o ModCount será modificado.
Em seguida, vamos verificar o código -fonte do ArrayList para ver como o ModCount é modificado.
pacote java.util; public class ArrayList <E> estende a lista de implementos abstrataList <E> <E>, RandomAccess, clonável, java.io.Serializable {... // Quando a capacidade muda na lista, a função de sincronização correspondente public void eSureCapApacacity (int minaPacity) {Modcount ++; int OldCapacity = ElementData.Length; if (mincapacity> OldCapacity) {objeto OldData [] = elementData; int newCapacity = (OldCapacity * 3)/2 + 1; if (newcapacity <mincapacity) newCapacity = MinCapacity; // MinCapacity geralmente é próximo do tamanho, então isso é uma vitória: elementData = Arrays.copyof (ElementData, NewCapacity); }} // Adicione o elemento ao último da fila public boolean add (e e) {// modifique o modCount ecurecapacity (tamanho + 1); // incrementos modCount !! ElementData [size ++] = e; retornar true; } // Adicione o elemento ao local especificado public void add (int index, e elemento) {if (índice> size || índice <0) lançar novo indexOutOfBoundSexception ("index:"+index+", tamanho:"+tamanho); // modifique o modCount SecurEcapacity (tamanho+1); // incrementos modCount !! System.ArrayCopy (ElementData, Index, ElementData, índice + 1, tamanho - índice); elementData [index] = elemento; tamanho ++; } // Adicione a coleção public boolean addall (coleção <? Extende e> c) {object [] a = c.toarray (); int numNew = A.Length; // modificar o modCount SurCapacity (tamanho + numNew); // incrementos modCount System.arraycopy (a, 0, elementData, tamanho, numNew); tamanho += numNew; retornar numNew! = 0; } // Exclua o elemento no local especificado public e Remover (int index) {Rangecheck (index); // modificar modCount modCount ++; E OldValue = (e) elementData [index]; int numMoved = size - índice - 1; if (numMoved> 0) System.arrayCopy (ElementData, Index+1, ElementData, Index, numMoved); elementData [-size] = null; // Deixe o GC fazer seu trabalho retornar OldValue; } // Excluir rapidamente os elementos no local especificado private void fastremove (int index) {// modificar modcount modCount ++; int numMoved = size - índice - 1; if (numMoved> 0) System.arrayCopy (ElementData, Index+1, ElementData, Index, numMoved); elementData [-size] = null; // Deixe o GC fazer seu trabalho} // limpe a coleção public void clear () {// modificar modCount modCount ++; // Deixe o GC fazer seu trabalho para (int i = 0; i <tamanho; i ++) elementData [i] = null; tamanho = 0; } ...} A partir disso, descobrimos que se é add (), remover () ou clear (), o valor do modCount será alterado desde que envolva a modificação do número de elementos no conjunto.
Em seguida, vamos resolver sistematicamente como o Fail-Fast é produzido. As etapas são as seguintes:
(01) Crie um novo nome Arraylist como ArrayList.
(02) Adicione conteúdo ao ArrayList.
(03) Crie um novo "Thread A" e leia o valor da Arraylist repetidamente através do iterador em "Thread A".
(04) Crie um novo "Thread B" e exclua um "nó A" na lista de Arraylist em "Thread B".
(05) Nesse momento, ocorrerão eventos interessantes.
Em algum momento, "Thread A" cria o iterador da Arraylist. Neste momento, "Node A" ainda existe na lista de Arraylist. Ao criar um ArrayList, esperadoModCount = modCount (assumindo que seus valores sejam n no momento).
Em algum momento, durante o processo de atravessar o Arraylist, "Thread B" executa e "Thread B" exclui o nó A "na Arraylist. Quando "Thread B" executa remover () para excluir operação, "ModCount ++" é executado em remover () e o modCount se torna n+1!
"Thread A" então atravessa. Quando executa a função Next (), o checkForComodification () é chamado para comparar os tamanhos do "esperado ModCount" e "ModCount"; e "esperadomodCount = n", "modCount = n+1", portanto, uma exceção concorrente de exceção da UMModificação é lançada, resultando em um evento de falha.
Neste ponto, temos um entendimento completo de como o Fail-Fast é produzido!
Ou seja, quando vários threads operam no mesmo conjunto, quando um thread acessa o conjunto, o conteúdo do conjunto é alterado por outros threads (ou seja, outros threads alteram o valor do ModCount através de add, remover, limpar e outros métodos); No momento, uma exceção concorrente de exceção será lançada, resultando em um evento de falha.
5. O princípio de resolver fracasso
O exposto acima explica os "métodos para resolver o mecanismo de falha" e também conhece a "causa raiz do fracasso". Em seguida, vamos falar mais sobre como resolver o evento Fail-Fast no pacote java.util.concurrent.
Vamos explicar com o copywriteArraylist correspondente ao ArrayList. Vamos primeiro dar uma olhada no código -fonte do copywriteArrayList:
pacote java.util.Concurrent; importar java.util.*; importar java.util.concurrent.locks.*; importar sun.misc.unsfe; classe pública copywriteArrayList <e> implementos list <e>, aleatória, clonável, java.io.sializable {... iterator () {retorna os métodos de implementação de falha rápida na nova classe de coleção são quase os mesmos. Vamos tomar o ArrayList mais simples como exemplo. transitório protegido int modCount = 0; Registra o número de vezes que modificamos o ArrayList. Por exemplo, quando chamamos add (), remover (), etc. Para alterar os dados, o ModCount ++ será alterado. transitório protegido int modCount = 0; Registra o número de vezes que modificamos o ArrayList. Por exemplo, quando chamamos add (), remover (), etc. Para alterar os dados, o ModCount ++ será alterado. Cowiterator <E> (getArray (), 0); } ... classe estática privada cowiterator <e> implementa o listiterator <E> {objeto final privado [] instantâneo; private int cursor; cowiterator privado (objeto [] elementos, int inicialCursor) {cursor = InitialCursor; // Ao criar um novo cowiterator, salve os elementos da coleção em uma nova matriz de cópias. // Dessa forma, quando os dados do conjunto original mudarem, os valores nos dados de cópia também não serão alterados. instantâneo = elementos; } public boolean hasNext () {return cursor <snapshot.length; } public boolean hasprevious () {return cursor> 0; } public e next () {if (! hasNext ()) lançar novos nosuchElementException (); return (e) instantâneo [cursor ++]; } public e anterior () {if (! hasPrevious ()) lançar novos nosuchElementException (); return (e) instantâneo [-cursor]; } public int nextIndex () {return cursor; } public int anteriorIndex () {return cursor-1; } public void REMOT () {THROW NEW UNSUPPORTEDOPERATIONECCECCECTION (); } public void Set (e E) {THROW NOVA UNSUPPORTEDOPERATIONEXCECTION (); } public void add (e e) {lança novo UnsupportEDoperationException (); }} ...} A partir disso, podemos ver:
(01) Diferentemente do ArrayList herda do AbstractList, o copyWriteArrayList não herda da AbstractList, ele apenas implementa a interface da lista.
(02) O iterador retornado pela função iterator () do ArrayList é implementado no abstrataList; enquanto copywonwritearraylist implementa o próprio iterador.
(03) Quando o próximo () é chamado na classe de implementação do ArrayList, "Call checkForComodification () para comparar os tamanhos de 'esperadoModCount' e 'modCount'"; No entanto, não existe o chamado checkForComodification () na classe de implementação do iterador de copywriteArrayList, e a concorrenteModificaçãoException não será lançada!
6. Resumo
Como o hashmap (Arraylist) não é seguro para threads, se outros threads modificarem o mapa durante o processo de uso do iterador (a modificação aqui se refere à modificação estrutural, não simplesmente para modificar elementos do conteúdo da coleta), então um campo de modificação concorrente será jogado, ou seja, a estratégia de falha é implementada por falha. ModCount é o número de modificações. Este valor será adicionado à modificação do conteúdo HashMap (ArrayList). Então, durante a inicialização do iterador, esse valor será atribuído ao modCount esperado do iterador.
Mas o comportamento Fail-Fast não é garantido, então a prática de confiar nessa exceção está errada