2010-04-26 11 views

risposta

42

Non esiste un comportamento garantito per ciò che accade quando il comando add viene chiamato contemporaneamente da due thread su ArrayList. Tuttavia, è stata la mia esperienza che entrambi gli oggetti sono stati aggiunti bene. La maggior parte dei problemi relativi alla sicurezza dei thread relativi agli elenchi riguarda l'iterazione durante l'aggiunta/rimozione. Nonostante ciò, consiglio vivamente di non utilizzare ArrayList di Vanilla con più thread e accesso simultaneo.

Vector era lo standard per gli elenchi concorrenti, ma ora lo standard è utilizzare lo Collections synchronized list.

Inoltre, raccomando caldamente Java Concurrency in Practice di Goetz et al, se avete intenzione di passare del tempo a lavorare con i thread in Java. Il libro tratta questo problema in modo molto più dettagliato.

+13

"è stata la mia esperienza che entrambi gli oggetti sono stati aggiunti bene" voglio solo sottolineare che questa è solo pura fortuna. Probabilmente la finestra per un problema di corruzione dei dati con ArrayList è estremamente piccola, ma esiste ancora –

+5

Giusto, ed è una specie del mio punto. Nella stragrande maggioranza dei casi non ci saranno problemi; ma non programmiamo per la maggior parte dei casi. Per questo motivo ho fortemente raccomandato di non usare ArrayList. – derivation

2

è possibile utilizzare List l = Collections.synchronizedList(new ArrayList()); se si desidera la versione thread-safe di arrayList.

0

http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html

noti che questa implementazione non è sincronizzato. Se più thread accedono contemporaneamente a un'istanza ArrayList e almeno uno dei thread modifica l'elenco in modo strutturale, deve essere sincronizzato esternamente.

Poiché non esiste alcuna sincronizzazione interna, ciò che teorizza non è plausibile.

Quindi, le cose non sono sincronizzate, con risultati spiacevoli e imprevedibili.

+0

Giusto, l'ho visto. Avevo pensato che sarebbe stata lanciata una "ConcurrentModificationException" che non sta accadendo per noi .. forse stiamo incappando in un altro lato dell'affetto di non usare una raccolta thread-safe .. –

+0

@Marcus che sembra essere il caso solo se modificare la lista mentre si itera su di essa. Inoltre, non vi è alcuna garanzia che questa eccezione verrà generata. – WhirlWind

1

java.util.concurrent ha un elenco di array thread-safe. ArrayList standard non è thread-safe e il comportamento quando più thread si aggiornano allo stesso tempo non è definito. Ci possono anche essere comportamenti strani con più lettori quando uno o più thread sta scrivendo allo stesso tempo.

3

È anche possibile ottenere null, ArrayOutOfBoundsException o qualcosa lasciato all'implementazione. HashMap s è stato osservato per entrare in un ciclo infinito nei sistemi di produzione. Non hai davvero bisogno di sapere cosa potrebbe andare storto, semplicemente non farlo.

È possibile utilizzare Vector, ma tende a capire che l'interfaccia non è abbastanza ricca. Probabilmente troverai nella maggior parte dei casi una struttura di dati diversa.

3

Il comportamento è probabilmente non definito poiché ArrayList non è protetto da thread. Se modifichi l'elenco mentre un Iterator lo sta interagendo, otterrai una ConcurrentModificationException. È possibile eseguire il wrapping di ArrayList con Collection.synchronizedList o utilizzare una raccolta thread-safe (ce ne sono molti) o semplicemente inserire le chiamate di aggiunta in un blocco sincronizzato.

14

Qualsiasi numero di cose potrebbe accadere. È possibile ottenere entrambi gli oggetti aggiunti correttamente. Potresti ottenere solo uno degli oggetti aggiunti. È possibile ottenere un'eccezione ArrayIndexOutOfBounds perché la dimensione dell'array sottostante non è stata regolata correttamente. Oppure possono accadere altre cose. Basti dire che non si può fare affidamento su alcun comportamento che si verifichi.

Come alternativa, è possibile utilizzare Vector, è possibile utilizzare Collections.synchronizedList, è possibile utilizzare CopyOnWriteArrayList oppure è possibile utilizzare un blocco separato. Tutto dipende da che cosa stai facendo e dal tipo di controllo che hai sull'accesso alla collezione.

3

Mi è venuto in mente il seguente codice per simulare un po 'uno scenario reale.

100 attività vengono eseguite in parallelo e aggiornano lo stato completato al programma principale. Io uso un CountDownLatch per attendere il completamento dell'attività.

import java.util.concurrent.*; 
import java.util.*; 

public class Runner { 

    // Should be replaced with Collections.synchronizedList(new ArrayList<Integer>()) 
    public List<Integer> completed = new ArrayList<Integer>(); 

    /** 
    * @param args 
    */ 
    public static void main(String[] args) { 
     Runner r = new Runner(); 
     ExecutorService exe = Executors.newFixedThreadPool(30); 
     int tasks = 100; 
     CountDownLatch latch = new CountDownLatch(tasks); 
     for (int i = 0; i < tasks; i++) { 
      exe.submit(r.new Task(i, latch)); 
     } 
     try { 
      latch.await(); 
      System.out.println("Summary:"); 
      System.out.println("Number of tasks completed: " 
        + r.completed.size()); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
     exe.shutdown(); 
    } 

    class Task implements Runnable { 

     private int id; 
     private CountDownLatch latch; 

     public Task(int id, CountDownLatch latch) { 
      this.id = id; 
      this.latch = latch; 
     } 

     public void run() { 
      Random r = new Random(); 
      try { 
       Thread.sleep(r.nextInt(5000)); //Actual work of the task 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
      completed.add(id); 
      latch.countDown(); 
     } 
    } 
} 

Quando ho eseguito l'applicazione di 10 volte e almeno 3 o 4 volte il programma non è stato stampato un numero corretto di attività completate. Idealmente dovrebbe stampare 100 (se non si verificano eccezioni). Ma in alcuni casi stava stampando 98, 99 ecc.

Così dimostra che gli aggiornamenti simultanei di ArrayList non daranno risultati corretti.

Se si sostituisce ArrayList con una versione sincronizzata, il programma restituisce i risultati corretti.

0

Si potrebbe utilizzare al posto di ArrayList();:

Collections.synchronizedList(new ArrayList()); 

o

new Vector(); 

synchronizedList come me preferibile perché è:

  • più veloce su 50-100%
  • può lavorare con Arra già esistente yList's