2013-09-24 5 views
5

Nella nostra applicazione abbiamo ottenuto un'eccezione ArrayIndexOutOfBounds sull'operazione ArrayList.add(Object o). La spiegazione più ovvia è la sicurezza del thread, ma non sono stato in grado di ricreare gli eventi. Ho provato a creare due thread. In uno sto aggiungendo elementi, nell'altro li sto rimuovendo (o cancellando l'array), ma non ho ottenuto l'eccezione per la seconda volta. Voglio dire, è ovvio che può succedere guardando la fonte di ArrayList, ma sarebbe bello poterlo dimostrare.Come dimostrare che l'arraylist non è thread-safe con un test?

Ho corso questa prova per un bel po 'di tempo, senza alcuna eccezione:

public class Test { 
static ArrayList a = new ArrayList(); 

public static void main(String[] args) throws Exception { 
    Thread t1 = new Thread() { 
     public void run() { 
      while (true) { 
       if (a.size() > 0) 
        a.remove(0); 
      } 
     } 
    }; 

    Thread t2 = new Thread() { 
     public void run() { 
      while (true) { 
       a.add(new Object()); 
      } 
     } 
    }; 

    t2.start(); 
    Thread.sleep(100); 
    t1.start(); 
} 
} 
+0

'ArrayList.add (Object)' non getterà 'ArrayIndexOutOfBoundsException'; 'Will ArrayList.add (index, Object)'. –

+0

guido: '' 'add (Object)' '' può lanciare anche l'eccezione (l'ho vista con i miei occhi) se lo stato interno dell'arrayylist è cambiato da altro thread. Controlla il codice sorgente. – NeplatnyUdaj

+0

sì hai ragione: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/ArrayList.java#ArrayList.add%28java.lang .Oggetto% 29; deve essere sincronizzato esternamente –

risposta

5

Grazie per commentare da isnot2bad ho trovato un problema nella mia ipotesi. Il problema è con gli aggiunti simultanei, non con l'aggiunta/rimozione. sono stato in grado di creare un test in mancanza:

static ArrayList a = new ArrayList(1); 

public static void main(String[] args) throws Exception { 
    Thread t1 = new Thread() { 
     public void run() { 
      while (true) { 
       a.add(new Object()); 
      } 
     } 
    }; 

    Thread t2 = new Thread() { 
     public void run() { 
      while (true) { 
       a = new ArrayList(1); 
       a.add(new Object()); 
       a.add(new Object()); 
      } 
     } 
    }; 

    t2.start(); 
    Thread.sleep(100); 
    t1.start(); 
} 

Sulla linea con l'aggiunta al primo thread, sto ottenendo questo:

Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 2 

:)

+1

Sostituire così meglio 'ArrayList' da una raccolta concorrente come' ConcurrentLinkedQueue' o semplicemente 'Collections.synchronizedList (new ArrayList())' nel tuo progetto! ;) – isnot2bad

+0

In questo caso particolare si. So dov'è il problema.Ma non l'ha mai visto prima. – NeplatnyUdaj

1

io sono in grado per riprodurre il problema semplicemente aggiungendo più thread di adder.

0

Fare in modo che il thread del consumatore dorma molto meno del sonno del produttore, ad es. 20 ms anziché 100 ms. In questo modo le probabilità che l'eccezione venga lanciata sono molto più grandi.

+0

I thread non stanno dormendo. È solo un ritardo prima di iniziare il secondo thread. Ad ogni modo quel codice non ha prodotto il comportamento desiderato – NeplatnyUdaj

2

Può essere difficile osservare qualsiasi errore con il codice dato perché in realtà non si controlla cosa è memorizzato nell'elenco. Non posso dire che sia impossibile ottenere uno ArrayIndexOutOfBoundsException ma sarà molto raro perché è possibile ottenerlo solo quando l'array viene ridimensionato e viene ridimensionato molto di rado.

Se si controlla che gli oggetti che si rimuovono non siano duplicati è molto più probabile vedere un comportamento imprevisto: si aggiungono solo nuovi oggetti, quindi il thread che rimuove non dovrebbe mai vedere lo stesso oggetto due volte, giusto? Non così:

import java.util.*; 
public class Test { 
    static ArrayList a = new ArrayList(); 

    public static void main(String[] args) throws Exception { 
     Thread t1 = new Thread() { 
      public void run() { 
       Object x = null; 
       while (true) { 
        if (a.size() > 0) { 
         Object y = a.remove(0); 
         if (x == y) System.out.println("Duplicate!"); 
         x = y; 
        } 
       } 
      } 
     }; 

     Thread t2 = new Thread() { 
      public void run() { 
       while (true) { 
        a.add(new Object()); 
       } 
      } 
     }; 

     t2.start(); 
     Thread.sleep(100); 
     t1.start(); 
    } 
} 

Ciò accade quando viene aggiunto un oggetto durante la chiamata System.arrayCopy: elementData[--size] = null imposta l'indice di matrice sbagliato null perché size non ha più il valore che aveva all'inizio del metodo.