Explanation
What just happened
ArrayList.add() is several steps: read size, write the element into slot size, then store size + 1. Both threads read size 0 before either had stored the new size. Both wrote slot 0, and both set size to 1. Two adds, one element. With a resize happening at the same moment, add() can even throw ArrayIndexOutOfBoundsException.
Try the what-if switch
- synchronizedList: every method runs inside
synchronizedon the list. T2 waitedBLOCKEDwhile T1 was insideadd(). Correct, but all threads queue for one lock. - ConcurrentHashMap: locking is per key (per bin). Counting different words never waits, and only updates to the same key take turns.
merge()does read-update-write as one atomic step.
Which to use
| Need | Use |
|---|---|
| Shared map | ConcurrentHashMap |
| List read often, changed rarely | CopyOnWriteArrayList |
| Hand work between threads | BlockingQueue |
| Quick fix for old code | Collections.synchronizedList / synchronizedMap |
Watch out
A thread-safe collection makes each call safe, not your sequence of calls:
counts.put(w, counts.get(w) + 1); // two calls: still loses updates
counts.merge(w, 1, Integer::sum); // one atomic call
Use the atomic methods: merge, compute, putIfAbsent, computeIfAbsent.
Common questions
Is ArrayList thread-safe?
No. Two threads calling add() at once can write into the same slot, so an element is lost, or add() throws ArrayIndexOutOfBoundsException during a resize. HashMap has the same problem.
What is the difference between synchronizedList and ConcurrentHashMap?
Collections.synchronizedList puts one lock around every call, so all threads take turns. ConcurrentHashMap locks per key (per bin): updates to different keys don't wait for each other.
Why does my ConcurrentHashMap still lose updates?
map.put(k, map.get(k) + 1) is two calls, and another thread can update in between. Use one atomic call instead: merge(k, 1, Integer::sum), compute or computeIfAbsent.
Verified: this timeline matches a real program, checked in CI on JDK 21 and 25. How we verify. Unclear or wrong? Tell us