Domanda

In un'applicazione multi-thread su cui sto lavorando, occasionalmente vediamo ConcurrentModificationExceptions nelle nostre liste (che sono per lo più ArrayList, a volte Vettori).Ma ci sono altri momenti in cui penso che si stiano verificando modifiche simultanee perché nell'iterazione della raccolta sembra che manchino elementi, ma non vengono generate eccezioni.So che i documenti per ConcurrentModificationException dice che non puoi fare affidamento su di esso, ma come potrei assicurarmi di non modificare contemporaneamente un elenco?E racchiudere ogni accesso alla raccolta in un blocco sincronizzato è l'unico modo per impedirlo?

Aggiornamento: Sì, lo so Collections.synchronizedCollection, ma non impedisce che qualcuno modifichi la raccolta mentre la stai scorrendo.Penso che almeno alcuni dei miei problemi si verifichino quando qualcuno aggiunge qualcosa a una raccolta mentre la sto scorrendo.

Secondo aggiornamento Se qualcuno vuole combinare la menzione di sincronizzatoCollection e clonazione come ha fatto Jason con una menzione di java.util.concurrent e dei framework di raccolte Apache come hanno fatto jacekfoo e Javamann, posso accettare una risposta.

È stato utile?

Soluzione

La tua domanda originale sembra richiedere un iteratore che veda gli aggiornamenti in tempo reale alla raccolta sottostante pur rimanendo thread-safe.Questo è un problema incredibilmente costoso da risolvere nel caso generale, motivo per cui nessuna delle classi di raccolta standard lo fa.

Esistono molti modi per ottenere soluzioni parziali al problema e, nella tua applicazione, uno di questi potrebbe essere sufficiente.

Jason dà un modo specifico per garantire la sicurezza del thread ed evitare di generare un'eccezione ConcurrentModificationException, ma solo a scapito della vivacità.

Javamann menziona due classi specifiche nel java.util.concurrent pacchetto che risolve lo stesso problema senza vincoli, dove la scalabilità è fondamentale.Questi venivano forniti solo con Java 5, ma ci sono stati vari progetti che trasferiscono la funzionalità del pacchetto nelle versioni Java precedenti, inclusi Questo, anche se non avranno prestazioni così buone nei JRE precedenti.

Se stai già utilizzando alcune delle librerie Apache Commons, allora come jacekfoo sottolinea, IL framework delle raccolte Apache contiene alcune classi utili.

Potresti anche considerare di guardare il file Struttura delle raccolte di Google.

Altri suggerimenti

A seconda della frequenza di aggiornamento, uno dei miei preferiti è CopyOnWriteArrayList o CopyOnWriteArraySet.Creano un nuovo elenco/insieme sugli aggiornamenti per evitare eccezioni di modifica simultanea.

Guardare java.util.concurrent per le versioni delle classi Collections standard progettate per gestire meglio la concorrenza.

Sì, devi sincronizzare l'accesso agli oggetti delle raccolte.

In alternativa, puoi utilizzare i wrapper sincronizzati attorno a qualsiasi oggetto esistente.Vedere Collezioni.synchronizedCollection().Per esempio:

List<String> safeList = Collections.synchronizedList( originalList );

Tuttavia, tutto il codice deve utilizzare la versione sicura e anche così l'iterazione durante le modifiche di un altro thread comporterà problemi.

Per risolvere il problema dell'iterazione, copiare prima l'elenco.Esempio:

for ( String el : safeList.clone() )
{ ... }

Per raccolte più ottimizzate e thread-safe, guarda anche java.util.concurrent.

Di solito ottieni una ConcurrentModificationException se stai tentando di rimuovere un elemento da un elenco mentre viene ripetuto.

Il modo più semplice per testarlo è:

List<Blah> list = new ArrayList<Blah>();
for (Blah blah : list) {
     list.remove(blah); // will throw the exception
}

Non sono sicuro di come potresti aggirare il problema.Potrebbe essere necessario implementare il proprio elenco thread-safe oppure creare copie dell'elenco originale per la scrittura e disporre di una classe sincronizzata che scriva nell'elenco.

Potresti provare a utilizzare la copia difensiva in modo che le modifiche a one List non influenzare gli altri.

Avvolgere gli accessi alla raccolta in un blocco sincronizzato è il modo corretto per farlo.La pratica di programmazione standard impone l'uso di una sorta di meccanismo di blocco (semaforo, mutex, ecc.) quando si ha a che fare con uno stato condiviso tra più thread.

A seconda del caso d'uso, tuttavia, di solito è possibile apportare alcune ottimizzazioni per bloccarlo solo in determinati casi.Ad esempio, se disponi di una raccolta che viene letta frequentemente ma scritta raramente, puoi consentire letture simultanee ma applicare un blocco ogni volta che è in corso una scrittura.Le letture simultanee causano conflitti solo se è in corso la modifica della raccolta.

ConcurrentModificationException è il massimo sforzo perché quello che stai chiedendo è un problema difficile.Non esiste un buon modo per farlo in modo affidabile senza sacrificare le prestazioni oltre a dimostrare che i modelli di accesso non modificano contemporaneamente l'elenco.

La sincronizzazione probabilmente impedirebbe modifiche simultanee e potrebbe essere ciò a cui ricorrere alla fine, ma può risultare costosa.La cosa migliore da fare è probabilmente sedersi e pensare per un po’ al proprio algoritmo.Se non riesci a trovare una soluzione senza blocchi, ricorri alla sincronizzazione.

Vedi l'implementazione.Fondamentalmente memorizza un int:

transient volatile int modCount;

e questo viene incrementato quando c'è una "modifica strutturale" (come rimuovere).Se l'iteratore rileva che modCount è cambiato, genera un'eccezione di modifica simultanea.

La sincronizzazione (tramite Collections.synchronizedXXX) non andrà bene poiché non garantisce la sicurezza dell'iteratore, sincronizza solo scritture e letture tramite put, get, set ...

Vedi java.util.concurennt e il framework delle raccolte apache (ha alcune classi ottimizzate che funzionano correttamente in un ambiente simultaneo quando ci sono più letture (non sincronizzate) che scritture - vedi FastHashMap.

È inoltre possibile sincronizzare le iterazioni dell'elenco.

List<String> safeList = Collections.synchronizedList( originalList );

public void doSomething() {
   synchronized(safeList){
     for(String s : safeList){
           System.out.println(s);

     }
   }

}

Ciò bloccherà l'elenco durante la sincronizzazione e bloccherà tutti i thread che tentano di accedere all'elenco mentre lo modifichi o esegui l'iterazione su di esso.Lo svantaggio è che si crea un collo di bottiglia.

Ciò consente di risparmiare memoria rispetto al metodo .clone() e potrebbe essere più veloce a seconda di ciò che stai facendo nell'iterazione...

Collezioni.synchronizedList() renderà un elenco nominalmente thread-safe e java.util.concurrent ha funzionalità più potenti.

Ciò eliminerà l'eccezione di modifica simultanea.Non parlerò comunque dell'efficienza ;)

List<Blah> list = fillMyList();
List<Blah> temp = new ArrayList<Blah>();
for (Blah blah : list) {
     //list.remove(blah);  would throw the exception
     temp.add(blah);
}
list.removeAll(temp);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top