Thread-safe collections
Choose between ConcurrentHashMap, CopyOnWriteArrayList and a synchronized wrapper, and know their limits.
Open this lesson in the learning hubKey points
ArrayListandHashMapare not thread-safe. Concurrent writes lose data and can corrupt the structure itself.ConcurrentHashMaplocks one bin at a time, so threads working on different keys never wait for each other.- Use its atomic methods:
merge,compute,putIfAbsent. A get followed by a put is still a race. CopyOnWriteArrayListcopies the whole array on every write. Ideal for listener lists, terrible for hot writes.Collections.synchronizedListlocks each call, but you still have to synchronize on the list while iterating it.- Thread-safe elements never make a compound action safe. Two safe calls in a row are still two separate steps.
Example
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws InterruptedException {
Map<String, Integer> counts = new ConcurrentHashMap<>();
List<String> events = new CopyOnWriteArrayList<>();
List<Integer> synced = Collections.synchronizedList(new ArrayList<>());
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 4; i++) {
int id = i;
pool.execute(() -> {
for (int n = 0; n < 1000; n++) {
counts.merge("hits", 1, Integer::sum); // one atomic step, nothing lost
synced.add(n);
}
events.add("worker-" + id);
});
}
pool.shutdown();
pool.awaitTermination(5, TimeUnit.SECONDS);
counts.computeIfAbsent("misses", k -> 0); // atomic check-then-act
System.out.println("ConcurrentHashMap : " + new TreeMap<>(counts));
System.out.println("synchronizedList : " + synced.size() + " items");
System.out.println("CopyOnWriteList : " + events.size() + " events");
long sum = 0;
synchronized (synced) { for (int v : synced) sum += v; } // iteration still needs the lock
System.out.println("iterated safely : sum " + sum);
}
}
A thread-safe collection protects each call, not your sequence of calls.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Multithreading course, and every lesson in it is listed on the Multithreading contents page.