Understanding Thread Safety Issues in Java Collections: ArrayList, HashSet, and HashMap
This article explains the thread‑unsafe nature of Java’s ArrayList, HashSet and HashMap, details their internal implementations and growth mechanisms, and presents three common solutions—Vector, synchronized wrappers, and CopyOnWrite collections—to achieve thread safety in concurrent environments.
1. Thread‑unsafe ArrayList
The Java Collection Framework includes List implementations such as ArrayList, Vector, and LinkedList. ArrayList is backed by an internal array ( elementData ) that starts as an empty array. When the first element is added, ensureCapacityInternal calculates a minimum capacity of 10 and triggers the grow() method, expanding the array to a capacity of 10.
Subsequent additions check the current capacity; when the size exceeds the array length, the array grows by 1.5× (e.g., from 10 to 15, then to 22, etc.) using newCapacity = oldCapacity + (oldCapacity >> 1) . The add(E e) operation performs elementData[size++] = e , which is not an atomic operation, making it unsafe in multithreaded contexts.
new ArrayList<Integer>();
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}1.3 Single‑threaded safety
In a single‑threaded scenario, adding custom BuildingBlockWithName objects to an ArrayList preserves order and content as expected.
class BuildingBlockWithName {
String shape;
String name;
public BuildingBlockWithName(String shape, String name) {
this.shape = shape;
this.name = name;
}
@Override
public String toString() {
return "BuildingBlockWithName{shape='" + shape + "',name=" + name + '}';
}
}
ArrayList<BuildingBlockWithName> list = new ArrayList<>();
list.add(new BuildingBlockWithName("三角形", "A"));
// ... add B, C, D, E1.4 Multithreaded unsafety
When 20 threads concurrently add random blocks to an ArrayList , a ConcurrentModificationException may be thrown because the internal array is modified without proper synchronization.
Exception in thread "10" java.util.ConcurrentModificationException1.5 Solutions to make ArrayList thread‑safe
Use Vector (methods are synchronized).
Wrap with Collections.synchronizedList(new ArrayList<>()) .
Use CopyOnWriteArrayList (writes copy the array, reads are lock‑free).
Vector
Vector initializes with capacity 10 and synchronizes its add method, guaranteeing thread safety at the cost of blocking performance.
public Vector() {
this(10);
}Collections.synchronizedList
The wrapper synchronizes all list operations (except iterator) by adding synchronized blocks.
List
list = Collections.synchronizedList(new ArrayList<>());CopyOnWriteArrayList
Writes acquire a ReentrantLock , copy the underlying array via Arrays.copyOf , add the element, and then replace the reference. The underlying array is volatile , ensuring visibility.
private volatile Object[] array;2. Thread‑unsafe HashSet
HashSet is backed by a HashMap . Adding an element stores the key as the element and a constant PRESENT object as the value.
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}Because the underlying HashMap operations are not synchronized, HashSet is also not thread‑safe.
2.4 Making HashSet thread‑safe
Collections.synchronizedSet(new HashSet<>())
CopyOnWriteArraySet (internally uses CopyOnWriteArrayList )
3. Thread‑unsafe HashMap
Like HashSet, HashMap is not safe for concurrent modifications.
Map
map = new HashMap<>();
map.put("A", new BuildingBlockWithName("三角形", "A"));3.2 Solutions for HashMap
Collections.synchronizedMap(new HashMap<>())
ConcurrentHashMap (segments/locks for finer‑grained concurrency)
3.3 ConcurrentHashMap principle
Internally divided into 16 segments; each segment can be locked independently, allowing parallel puts when keys fall into different segments.
4. Other collection classes
LinkedList, TreeSet, LinkedHashSet, TreeMap are also thread‑unsafe; Hashtable is thread‑safe.
Summary
The article details the internal growth mechanisms of ArrayList , demonstrates its thread‑unsafe behavior, and provides three approaches—Vector, synchronized wrappers, and CopyOnWrite collections—to achieve thread safety. It similarly covers HashSet and HashMap , offering synchronized wrappers and concurrent alternatives, and compares ReentrantLock with synchronized .
Wukong Talks Architecture
Explaining distributed systems and architecture through stories. Author of the "JVM Performance Tuning in Practice" column, open-source author of "Spring Cloud in Practice PassJava", and independently developed a PMP practice quiz mini-program.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.