2010-10-15 15 views
5

Ho un'applicazione multithread che ha un elenco centrlaised che viene aggiornato (scritto in) solo dal thread principale. Ho quindi diversi altri thread che hanno bisogno di recuperare periodicamente l'elenco nel suo stato attuale. C'è un metodo che mi può permettere di fare questo?JAVA: Controllo della concorrenza per l'accesso all'elenco in java

+0

Quale versione del JDK stai usando? Se 1.5+, ci sono alcune strutture di dati predefinite che fanno questo per te, vedi Javadoc. –

risposta

12

Dipende da come si desidera limitare la concorrenza. Il modo più semplice è probabilmente usare CopyOnWriteArrayList. Quando si estrae un iteratore da esso, l'iteratore rispecchierà in che modo l'elenco ha esaminato il momento in cui è stato creato l'iteratore - le modifiche successive non saranno visibili all'iteratore. Il lato positivo è che può far fronte a un bel po 'di contesa, lo svantaggio è che l'aggiunta di nuovi elementi è piuttosto costosa.

L'altro modo di procedere è il blocco, il modo più semplice è probabilmente il wrapping dell'elenco con Collections.synchronizedList e la sincronizzazione nell'elenco durante l'iterazione.

Un terzo modo consiste nell'utilizzare una specie di BlockingQueue e inserire i nuovi elementi negli operai.

Modifica: Dato che l'OP ha indicato solo un'istantanea è necessaria, CopyOnWriteArrayList è probabilmente la migliore alternativa pronta all'uso.Un'alternativa (per meno di aggiungere, ma più costoso lettura) è solo la creazione di una copia di un synchronizedList quando è necessario traversion (copy-on-leggere piuttosto che copy-on-write):

List<Foo> originalList = Collections.synchronizedList(new ArrayList()); 

public void mainThread() { 
    while(true) 
     originalList.add(getSomething()); 
} 

public void workerThread() { 
    while(true) { 
     List<Foo> copiedList; 
     synchronized (originalList) { 
      copiedList = originalList.add(something); 
     } 
     for (Foo f : copiedList) process(f); 
    } 
} 

Edit: Vieni a pensarci bene, la versione copy-on-lettura potrebbe semplificato un po 'per evitare tutti i synchronized blocchi:

List<Foo> originalList = Collections.synchronizedList(new ArrayList()); 

public void mainThread() { 
    while(true) 
     originalList.add(getSomething()); 
} 

public void workerThread() { 
    while(true) { 
     for (Foo f : originalList.toArray(new Foo[0])) 
      process(f); 
    } 
} 

Edit 2: Ecco un semplice wrapper per una lista di copia-on-lettura che non lo fa usa qualsiasi aiuto e cerca di essere a grana fine nel blocco possibile (ho volutamente fatta un po 'eccessivo, al limite non ottimale, per dimostrare dove c'è bisogno di bloccaggio):

class CopyOnReadList<T> { 

    private final List<T> items = new ArrayList<T>(); 

    public void add(T item) { 
     synchronized (items) { 
      // Add item while holding the lock. 
      items.add(item); 
     } 
    } 

    public List<T> makeSnapshot() { 
     List<T> copy = new ArrayList<T>(); 
     synchronized (items) { 
      // Make a copy while holding the lock. 
      for (T t : items) copy.add(t); 
     } 
     return copy; 
    } 

} 

// Usage: 
CopyOnReadList<String> stuff = new CopyOnReadList<String>(); 
stuff.add("hello"); 
for (String s : stuff.makeSnapshot()) 
    System.out.println(s); 

Fondamentalmente, quando di bloccare quando:

  1. ... aggiungete un articolo alla lista.
  2. ... scorrere l'elenco per fare una copia di esso.
+0

+1 CopyOnWriteArrayList è il modo per passare da ciò che vedo nella domanda. BlockingQueue non ha senso data la domanda. –

+0

+1 - buon riassunto. Un modo più semplice per farlo con il blocco è di restituire una copia O (n) della lista su richiesta. L'approccio elenco sincronizzato richiede che sia la classe che tutti i destinatari della lista accedano all'elenco tramite il wrapper sincronizzato; e le iterazioni richiedono la sincronizzazione nell'elenco. –

+0

Devo sincronizzare anche le scritture per questo elenco? Potrei avere una sfumatura del processo di scrittura? – pie154

0

Se si desidera semplicemente un'istantanea di sola lettura e la lista non è troppo grande:

private static List<TypeHere> getCurrentList() { 
    /* code to return the list as it currently is */ 
} 
public static List<TypeHere> snapshot() { 
    TypeHere[] objects = getCurrentList().toArray(new TypeHere[] {}); 
    return Collections.unmodifiableList(Arrays.asList(objects)); 
} 
+0

È un'applicazione multi-thread. Forse qualche sincronizzazione sarebbe utile qui? –

+1

'toArray()' non sarà sicuro se l'elenco sottostante non lo è, e poiché questa è presumibilmente una classe wrapper thread-safe per liste arbitrarie, non funzionerà. In alternativa, se si richiede che l'elenco sottostante abbia comunque la semantica della concorrenza desiderata, non vedo cosa stia aggiungendo questo wrapper. –

1

Se i fili sono solo "leggendo" l'elenco si è al sicuro con solo usando List. Se la lista sarà "scritta su" (cambiata in qualsiasi modo) usa Vector invece di Lista.

+0

+1 Vettore - Elenco sincronizzato – tylermac

2

Dai uno sguardo allo Java's Concurrency Package. Ci dovrebbe essere qualcosa che puoi usare.

I thread figli devono essere di sola lettura? Hai solo bisogno di un articolo in cima alla lista? In che modo l'elenco viene utilizzato, potrebbe aiutarci a capire meglio il tuo problema e indirizzarti verso una direzione più chiara.

Per passare un'istantanea dell'elenco, è sufficiente creare una nuova lista popolata dall'elenco originale.

List<Object> newList; 
synchronize (originalList) 
{ 
    newList = new ArrayList<Object>(originalList); 
} 
return newList; 

Sincronizzato può o non può essere utile qui. Non ne sono sicuro.

+0

I thread secondari richiedono un'istantanea dell'intero elenco nel momento in cui lo leggono. – pie154

+0

È possibile creare un'istantanea passando una nuova lista con gli stessi elementi. Risposta aggiornata con un esempio – tylermac

+0

La sincronizzazione è assolutamente necessaria in questo. E le scritture devono essere sincronizzate. Il costruttore di copie esegue un'iterazione dell'elenco originale e, se l'elenco originale viene modificato mentre viene eseguita la copia, verrà visualizzato un ConcurrentModificationException. – sjlee

3

È possibile considerare l'utilizzo di un meccanismo di blocco di lettura-scrittura. Se la tua versione di JDK è 1.5 o più recente, puoi usare ReentrantReadWriteLock.

0

Se la scrittura non è frequente e la dimensione dei dati è piccola, CopyOnWriteArrayList è un'ottima scelta. Altrimenti, la tua performance in scrittura potrebbe essere un problema.

Se non è necessaria un'interfaccia List completa (principalmente l'accesso casuale tramite indice), sono disponibili più opzioni. Se il tuo caso d'uso è soddisfatto dell'interfaccia Queue, cose come ConcurrentLinkedQueue sarebbero una scelta eccellente. Se riesci a vivere con l'interfaccia Queue, diventa possibile qualcosa di simile:

Queue<Foo> originalList = new ConcurrentLinkedQueue<Foo>(); 

public void mainWrite() { 
    // main thread 
    originalList.add(getSomething()); // no locking needed 
} 

public void workerRead() { 
    // executed by worker threads 
    // iterate without holding the lock 
    for (Foo f: originalList) { 
     process(f); 
    } 
} 
Problemi correlati