Java Language
Collezioni simultanee
Ricerca…
introduzione
Collezioni thread-safe
Per impostazione predefinita, i vari tipi di raccolta non sono thread-safe.
Tuttavia, è abbastanza facile creare una raccolta sicura per i thread.
List<String> threadSafeList = Collections.synchronizedList(new ArrayList<String>());
Set<String> threadSafeSet = Collections.synchronizedSet(new HashSet<String>());
Map<String, String> threadSafeMap = Collections.synchronizedMap(new HashMap<String, String>());
Quando crei una raccolta thread-safe, non devi mai accedervi tramite la raccolta originale, solo attraverso il wrapper thread-safe.
A partire da Java 5, java.util.collections
ha diverse nuove raccolte thread-safe che non richiedono i vari metodi Collections.synchronized
.
List<String> threadSafeList = new CopyOnWriteArrayList<String>();
Set<String> threadSafeSet = new ConcurrentHashSet<String>();
Map<String, String> threadSafeMap = new ConcurrentHashMap<String, String>();
Collezioni simultanee
Le raccolte concorrenti sono una generalizzazione di raccolte thread-safe che consentono un utilizzo più ampio in un ambiente concorrente.
Sebbene le raccolte thread-safe abbiano un'aggiunta o rimozione sicura degli elementi da più thread, non necessariamente hanno un'iterazione sicura nello stesso contesto (potrebbe non essere possibile iterare in modo sicuro attraverso la raccolta in un thread, mentre un altro lo modifica aggiungendo / rimozione di elementi).
È qui che vengono utilizzate le raccolte simultanee.
Poiché l'iterazione è spesso l'implementazione di base di diversi metodi di massa nelle raccolte, come addAll
, removeAll
o anche la raccolta di copie (tramite un costruttore o altri mezzi), l'ordinamento, ... il caso d'uso per le raccolte concorrenti è in realtà piuttosto ampio.
Ad esempio, Java SE 5 java.util.concurrent.CopyOnWriteArrayList
è un'implementazione di Lis
t thread-safe e simultanea, i suoi stati javadoc :
Il metodo di iterazione di stile "snapshot" utilizza un riferimento allo stato dell'array nel punto in cui è stato creato l'iteratore. Questo array non cambia mai durante il ciclo di vita dell'iteratore, quindi l'interferenza è impossibile e l'iteratore è garantito non lanciare ConcurrentModificationException.
Pertanto, il seguente codice è sicuro:
public class ThreadSafeAndConcurrent {
public static final List<Integer> LIST = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws InterruptedException {
Thread modifier = new Thread(new ModifierRunnable());
Thread iterator = new Thread(new IteratorRunnable());
modifier.start();
iterator.start();
modifier.join();
iterator.join();
}
public static final class ModifierRunnable implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 50000; i++) {
LIST.add(i);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static final class IteratorRunnable implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 10000; i++) {
long total = 0;
for(Integer inList : LIST) {
total += inList;
}
System.out.println(total);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Un'altra raccolta concomitante relativa all'iterazione è ConcurrentLinkedQueue
, che afferma:
Gli iteratori sono debolmente coerenti, elementi di ritorno che riflettono lo stato della coda a un certo punto o dalla creazione dell'iteratore. Non gettano java.util.ConcurrentModificationException e possono procedere in concomitanza con altre operazioni. Gli elementi contenuti nella coda dalla creazione dell'iteratore verranno restituiti esattamente una volta.
Si dovrebbe controllare il javadocs per vedere se una raccolta è concomitante, oppure no. Gli attributi dell'iteratore restituiti dal metodo iterator()
("fail fast", "weakly consistent", ...) è l'attributo più importante da cercare.
Filetto di esempi sicuri ma non simultanei
Nel codice sopra, cambiando la dichiarazione LIST
su
public static final List<Integer> LIST = Collections.synchronizedList(new ArrayList<>());
Potrebbe (e statisticamente sulla maggior parte delle architetture CPU / core moderne) portare a delle eccezioni.
Le raccolte sincronizzate dai metodi di utilità Collections
sono thread-safe per l'aggiunta / rimozione di elementi, ma non l'iterazione (a meno che la raccolta sottostante non sia già passata ad essa).
Inserimento in ConcurrentHashMap
public class InsertIntoConcurrentHashMap
{
public static void main(String[] args)
{
ConcurrentHashMap<Integer, SomeObject> concurrentHashMap = new ConcurrentHashMap<>();
SomeObject value = new SomeObject();
Integer key = 1;
SomeObject previousValue = concurrentHashMap.putIfAbsent(1, value);
if (previousValue != null)
{
//Then some other value was mapped to key = 1. 'value' that was passed to
//putIfAbsent method is NOT inserted, hence, any other thread which calls
//concurrentHashMap.get(1) would NOT receive a reference to the 'value'
//that your thread attempted to insert. Decide how you wish to handle
//this situation.
}
else
{
//'value' reference is mapped to key = 1.
}
}
}