Java Language
Współbieżne kolekcje
Szukaj…
Wprowadzenie
Kolekcje bezpieczne dla wątków
Domyślnie różne typy kolekcji nie są bezpieczne dla wątków.
Jednak dość łatwo jest uczynić kolekcję bezpieczną dla wątków.
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>());
Kiedy tworzysz kolekcję bezpieczną dla wątków, nigdy nie powinieneś uzyskiwać do niej dostępu poprzez oryginalną kolekcję, tylko poprzez opakowanie bezpieczne dla wątków.
Począwszy od Java 5, java.util.collections
ma kilka nowych bezpiecznych dla wątków kolekcji, które nie wymagają różnych metod Collections.synchronized
.
List<String> threadSafeList = new CopyOnWriteArrayList<String>();
Set<String> threadSafeSet = new ConcurrentHashSet<String>();
Map<String, String> threadSafeMap = new ConcurrentHashMap<String, String>();
Współbieżne kolekcje
Współbieżne kolekcje to uogólnienie kolekcji bezpiecznych dla wątków, które umożliwiają szersze użycie w środowisku współbieżnym.
Podczas gdy kolekcje bezpieczne dla wątków mają bezpieczne dodawanie lub usuwanie elementów z wielu wątków, niekoniecznie mają one bezpieczne iteracje w tym samym kontekście (jeden może nie być w stanie bezpiecznie iterować kolekcji w jednym wątku, podczas gdy inny modyfikuje go poprzez dodanie / usuwanie elementów).
To tutaj używane są równoległe kolekcje.
Ponieważ iteracja jest często podstawową implementacją kilku metod zbiorczych w kolekcjach, takich jak addAll
, removeAll
, a także kopiowanie kolekcji (za pomocą konstruktora lub w inny sposób), sortowanie, ... przypadek użycia jednoczesnych kolekcji jest w rzeczywistości dość duży.
Na przykład Java SE 5 java.util.concurrent.CopyOnWriteArrayList
jest bezpieczną i równoczesną implementacją Lis
t, jej javadoc stwierdza:
Metoda iteratora typu „migawka” wykorzystuje odniesienie do stanu tablicy w punkcie, w którym iterator został utworzony. Ta tablica nigdy się nie zmienia w czasie życia iteratora, więc interferencja jest niemożliwa, a iterator gwarantuje, że nie zgłosi wyjątku ConcurrentModificationException.
Dlatego następujący kod jest bezpieczny:
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();
}
}
}
}
Innym współbieżnym zbiorem dotyczącym iteracji jest ConcurrentLinkedQueue
, który stwierdza:
Iteratory są słabo spójne, zwracając elementy odzwierciedlające stan kolejki w pewnym momencie w lub po utworzeniu iteratora. Nie zgłaszają wyjątku java.util.ConcurrentModificationException i mogą postępować jednocześnie z innymi operacjami. Elementy zawarte w kolejce od momentu utworzenia iteratora zostaną zwrócone dokładnie raz.
Należy sprawdzić javadocs, aby zobaczyć, czy kolekcja jest zbieżna, czy nie. Najważniejsze atrybuty, które należy zwrócić, to atrybuty iteratora zwrócone przez metodę iterator()
(„szybki brak”, „słabo spójny”, ...).
Bezpieczne wątki, ale nie równoległe przykłady
W powyższym kodzie zmiana deklaracji LIST
na
public static final List<Integer> LIST = Collections.synchronizedList(new ArrayList<>());
Może (i statystycznie będzie to dotyczyć większości nowoczesnych architektur z wieloma procesorami / rdzeniami) prowadzić do wyjątków.
Zsynchronizowane kolekcje z metod narzędziowych Collections
są wątkowo bezpieczne dla dodawania / usuwania elementów, ale nie iteracji (chyba że podstawowa kolekcja, która jest do niej przekazywana, już jest).
Wstawienie do 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.
}
}
}