2016-02-09 10 views
11

Ho un productList mantenuto in un file chiamato Products.javaDoes Collections.unmodifiableList (elenco) richiede un blocco?

private List<String> productList = Collections.synchronizedList(new ArrayList()); 

Ora la creazione di un elenco sincronizzato, farà in modo che operazioni come aggiungere/rimuovere avranno una serratura implicita e non ho bisogno di bloccare queste operazioni esplicitamente dichiara.

Ho una funzione esposta che restituisce un unmodifiableList di questo elenco.

public List getProductList(){ 

return Collections.unmodifiableList(productList); 
} 

Nella mia applicazione, vari thread possono chiamare questa funzione allo stesso tempo. Quindi devo inserire un blocco synchronized quando converti un Elenco in un Elenco non modificabile o questo sarà già risolto poiché sto usando una lista sincronizzata?

TIA.

+0

Ricerca correlata ione: http://stackoverflow.com/questions/35281056/how-to-synchronize-unmodifiable-collections –

risposta

5

Non è necessario che sia sincronizzato poiché l'elenco non modificabile sta eseguendo il wrapping di quello sincronizzato. Ma non c'è molto uso per la sincronizzazione in un elenco non modificabile, non per lo scopo di iterazione, che requires sincronizzazione manuale indipendentemente:

È indispensabile che l'utente sincronizzare manualmente sul restituito lista quando iterazioni su di esso:

List list = Collections.synchronizedList(new ArrayList()); 
    ... 
synchronized (list) { 
    Iterator i = list.iterator(); // Must be in synchronized block 
    while (i.hasNext()) 
     foo(i.next()); 
} 

La mancata osservanza di questo consiglio può comportare un comportamento non deterministico.

MODIFICA: come indicato da Ferrybig, in realtà non è possibile effettuare la sincronizzazione in modo sicuro con il wrapper non modificabile. Si consiglia di prendere in considerazione una soluzione alternativa per la sicurezza del thread, ad esempio CopyOnWriteArrayList.

+0

"Ma non c'è molto da usare per la sincronizzazione su un elenco non modificabile" questa affermazione è nel migliore dei casi fuorviante e nel peggiore sbagliato. Non è solo il codice che scrive su dati condivisi che devono occuparsi del blocco. Il codice che legge i dati condivisi deve utilizzare un lucchetto o un meccanismo che garantisca una pubblicazione sicura. – Raedwald

0

No È necessario inserire il blocco sincronizzato.

+2

Come puoi vedere nelle risposte di shmosel e Ferrybig, questo non è corretto. È anche piuttosto inutile come risposta, dal momento che non fornisci alcuna informazione per eseguire il backup della tua dichiarazione. –

4

L'unico posto dove il sincronizzato dovrebbe essere utilizzato è quando si esegue un ciclo su di esso, come spiegato dal javadoc:

E 'indispensabile che l'utente sincronizzare manualmente nella lista restituita quando l'iterazione su di esso:

Tuttavia, non è possibile farlo una volta avvolto in un unmodifiableList, rendendolo non sicuro per un risultato di ritorno. Può restituire dati corrotti in caso di accesso concorrente.

Invece di restituire l'elenco back-end, potrebbe essere preferibile restituire una copia del back-end, quindi la chiamata non deve preoccuparsi delle prestazioni sincronizzate.

public List getProductList(){ 
    synchronized (productList) { 
     return new ArrayList<>(productList); 
    } 
} 
+0

Se seguo l'approccio di restituire un nuovo ArrayList, durante il periodo in cui accedo all'elenco restituito, alcuni elementi potrebbero essere aggiunti o eliminati nell'elenco originale che non voglio perdere. Ho sempre bisogno della lista originale nel mio caso. – thedarkpassenger

+0

In tal caso, è possibile che si desideri rendere l'elenco di backend un 'CopyOnWriteArrayList' come @shmosel ha detto, tali elenchi non devono essere racchiusi in un blocco 'synchronizedList' e non subiscono' ConcurrentModificationException' durante il ciclo. Ti consiglio di aspettare almeno un'ora prima di accettare qualsiasi risposta, vedo che questa domanda sta diventando popolare. – Ferrybig

0

La soluzione più semplice è quello di ottenere una fotografia istantanea ogni volta,

list = get snapshot 
{ 
    work on the list 
} 
discard list // GC it 

poiché l'istantanea è una struttura di dati congelato costante, client può accedere liberamente.

Ma se il client accede a una struttura dati che può essere modificata da un'altra parte, diventa problematico.Dimentica i problemi di concorrenza; pensare la semantica del codice seguente

{ 
    n = list.size(); 
    list.get(n-1); 
} 

get(n-1) può fallire, perché l'elenco potrebbe essere ridotto quando si chiama.

avere un qualche tipo di garanzia di coerenza, il lato client deve fornire demarcazioni transazione esplicita durante la sessione di accesso, come

acquire lock // preferably a read-lock 
{ 
    work on the list 
} 
release lock 

Si noti che questo codice non è più semplice rispetto alla soluzione istantanea. E il cliente può ancora "perdere" gli aggiornamenti, proprio come la soluzione istantanea.

E si deve decidere se si desidera forzare i codici client a eseguire questo tipo di blocco.

Ovviamente non è privo di meriti; può essere migliore nelle prestazioni rispetto alla soluzione snapshot se l'elenco è grande e gli aggiornamenti non sono frequenti.

Se questo approccio è più appropriato per l'applicazione, possiamo progettare qualcosa di simile

interface CloseableList extends List, Closeable {} 

public CloseableList readProducts() // acquire read-lock when called 

-- client code 
try{CloseableList list = readProducts()) 
{ 
    .... 
} 
// on close(), release the read-lock 

Se il cliente ha solo bisogno di iterare i prodotti, siamo in grado di utilizzare java8 flusso

final ReadWriteLock lock = ... 

private ArrayList productList = new ArrayList(); 
// modifications to `productList` must be protected by write lock 

// javadoc: reader must close the stream! 
public Stream readProducts() 
{ 
    lock.readLock().lock(); 
    return productList.stream().onClose(lock.readLock()::unlock); 
} 

-- client code 
try(Stream products = readProducts()) 
{ 
    ... 
} 

Possiamo anche progettare l'API per prendere il codice cliente in modo da poterlo circondare con protezioni

public void readProducts(Consumer<List> action) 
{ 
    lock read 

    action.accept(productList); 

    finally unlock read 
} 

-- client code 
readProducts(list-> 
{ 
    ... 
});