Fundamentals 15 min read

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.

Wukong Talks Architecture
Wukong Talks Architecture
Wukong Talks Architecture
Understanding Thread Safety Issues in Java Collections: ArrayList, HashSet, and HashMap

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, E

1.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.ConcurrentModificationException

1.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 .

JavaConcurrencyThread SafetyCollectionsArrayList
Wukong Talks Architecture
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.