Table of Contents:
- 1️⃣ HashMap / TreeMap / TreeSet (not thread-safe)
- 2️⃣ SynchronizedMap / SynchronizedSortedMap / SynchronizedSortedSet
- 3️⃣ ConcurrentHashMap
- 4️⃣ ConcurrentSkipListMap / ConcurrentSkipListSet
- 5️⃣ AtomicInteger / AtomicReference / AtomicLong
- Tables for Comparison: One Element, Two Threads
- Table: one element is edited by two threads + new key is added
- Comparison of Thread-Safe Sorted Collections
- 🔹 Conclusions
- 🔹 Practical Rule
Understanding multithreading in Java through collections and atomics
Understanding Multithreading in Java through Collections and Atomics
1️⃣ HashMap / TreeMap / TreeSet (not thread-safe)
HashMap:- Structure: array of buckets + linked lists / trees (for collisions).
- Under the hood: on put/remove, the array of buckets is modified and, possibly, the chains are reordered.
- Problem with multithreading: two threads can simultaneously change one bucket → corrupt structures, loss of elements, infinite loop during iteration.
- Structure: red-black tree.
- On insertion/deletion, the thread modifies several nodes of the tree.
- Concurrent modifications → the structure of the tree can break, get/put/rebalance can throw ConcurrentModificationException or damage the balancing.
✅ Conclusion: external synchronization is needed; otherwise, the map/set easily gets corrupted.
2️⃣ SynchronizedMap / SynchronizedSortedMap / SynchronizedSortedSet
Under the hood: All methods are wrapped in synchronized (this) on the map/set object. Any operation locks the entire map/set during execution. Guarantees data integrity, but no parallel operation.
public V put(K key, V value) {
synchronized (this) {
return m.put(key, value);
}
}
Read/write → one operation at a time, which eliminates race condition.
3️⃣ ConcurrentHashMap
Under the hood (Java 8+):
- Array of buckets + nodes in trees for collisions.
- Main synchronization:
- get — completely lock-free.
- put — CAS (Compare-And-Swap) at the bucket level or synchronized at the tree node, only if needed.
- Table resizing partially synchronized at segments.
- Concurrent put/get on different keys — executed in parallel without locking the entire map.
- Race condition on value: if it is a mutable object inside the map, CHM does not synchronize its fields.
4️⃣ ConcurrentSkipListMap / ConcurrentSkipListSet
- Structure: Skip List (multi-layer linked list).
- Operations use lock-free algorithms + CAS.
- Each level of the list is partially blocked or updated atomically.
- Allows parallel operations on different keys.
- Simultaneous changes of one key → race condition, if atomic methods (compute, putIfAbsent) are not used.
5️⃣ AtomicInteger / AtomicReference / AtomicLong
- Uses CPU instructions CAS (Compare-And-Swap).
- Update is atomic and lock-free: the same value will not be lost in concurrent threads.
- Example:
AtomicInteger ai = new AtomicInteger(0); ai.incrementAndGet(); // atomic - No locks at the JVM or object level — pure CAS on CPU.
Tables for Comparison: One Element, Two Threads
| Collection / Object | Thread 1 → Thread 2 Arrived a Little Later | Thread 1 ↔ Thread 2 Started Simultaneously | Under the Hood / Comment |
|---|---|---|---|
| HashMap | ❌ race condition → value may be lost, structure is fine (if only one field) | ❌ race condition → may corrupt the structure, data loss | No synchronization, everything at the discretion of CPU threads |
| TreeMap / TreeSet | ❌ race condition → corrupt tree or data loss | ❌ race condition → corrupt tree | No thread-safety, node manipulations are not atomic |
| SynchronizedMap / SynchronizedSortedMap / SynchronizedSortedSet | ✅ safe, thread 2 waits | ✅ safe, one thread waits for another | Monitor blocks the entire map/set for operation |
| ConcurrentHashMap | ⚠️ map structure is fine, but value race condition: last assignment wins | ⚠️ CAS at bucket or node level → last assignment wins | Map structure lock-free, object field is not protected |
| ConcurrentSkipListMap / Set | ⚠️ skip list structure is correct, value race condition | ⚠️ last assignment wins | CAS at node level, value object is not atomic |
| AtomicInteger / AtomicReference / AtomicLong | ✅ atomic, thread 2 waits in CAS only on conflict, value increases correctly | ✅ atomic, CAS guarantees one update → second retries | CPU CAS, lock-free, race condition excluded |
Table: one element is edited by two threads + new key is added
| Collection / object | Code example | Thread 1+2 edit element | Thread 3 adds new key | Under the hood / comment | Data loss / corrupt |
|---|---|---|---|---|---|
| HashMap | |
❌ race condition, one update may be lost | ❌ adding a new key may break the structure | No synchronization, all threads change buckets simultaneously | High risk of corrupt |
| TreeMap / TreeSet | |
❌ race condition, the tree structure may break | ❌ adding a new key changes the nodes of the tree | No thread safety, tree balancing breaks | High risk of corrupt |
| SynchronizedMap / SynchronizedSortedMap / SynchronizedSortedSet | |
✅ Thread 1 and 2 execute sequentially (monitor lock) | ✅ Thread 3 waits for completion | All methods are synchronized on the object, iteration requires a lock | Low, safe |
| ConcurrentHashMap | |
⚠️ Race condition on value, map structure is correct | ✅ Adding new key in parallel | Lock-free buckets + CAS, reading lock-free | Value may be lost, structure is fine |
| ConcurrentSkipListMap / Set | |
⚠️ Race condition on value | ✅ Adding new key in parallel | Skip list + CAS, lock-free | Value may be lost, structure is fine |
| AtomicInteger / AtomicReference | |
✅ atomically, both threads correctly update the value | ✅ adding a new key through a separate AtomicReference object is safe | CAS at the CPU level, lock-free | Low, safe |
🔹 Key points:
- SynchronizedMap — methods lock the entire object, so Thread 1 and Thread 2 never execute simultaneously.
- ConcurrentHashMap — the map structure is safe, but the object value is not atomic, so AtomicInteger or the
computemethod is needed. - Atomic objects — completely atomic, race condition is excluded.
Comparison of Thread-Safe Sorted Collections
1️⃣ Collections.synchronizedSortedMap(new TreeMap<>())
Collections.synchronizedSortedMap(new TreeMap<>())
Thread A: ─────[took monitor]─────────────> working
Thread B: ─────[waiting, monitor busy]─────┐
Thread C: ─────[waiting, monitor busy]─────┘
All operations block each other: get, put, remove, containsKey — only one thread inside at the same time.
Output:
Real tree (red-black).
Full sorting is preserved.
But there is no parallelism — everyone is waiting.
2️⃣ ConcurrentSkipListMap
Level 3: 10 --------- 30 ---------------- 75 --------- 90
Level 2: 10 ---- 25 ---- 50 ---- 60 ---- 75 ---- 90
Level 1: 10 20 25 30 40 50 60 70 75 80 90
Thread A: inserts 35
Thread B: inserts 55
Thread C: reads 30
✔ All three operations can be executed simultaneously because locks are only taken on small segments of the list.
✔ The order of keys is preserved, submaps work.
Output:
Not a tree, but a skip-list.
Sorted keys as in TreeMap.
Multithreaded, parallel, without global locking.
| Property | SynchronizedSortedMap(TreeMap) | ConcurrentSkipListMap |
|---|---|---|
| Structure | Red-black tree | Skip-list |
| Order of keys | Yes | Yes |
| Thread-safety | Yes, via synchronized | Yes, via fine-grained locks |
| Parallel operation | No, one thread | Yes, multiple threads simultaneously |
| Support for subMap/headMap | Yes | Yes |
| Performance with many threads | Decreases | Good |
🔹 Conclusions
HashMap / TreeMap / TreeSet
- Not thread-safe.
- Any simultaneous changes can lead to data loss, structure corruption, and infinite iterations.
- External synchronization is required (e.g.,
synchronizedblock).
SynchronizedMap / SynchronizedSortedMap / SynchronizedSortedSet
- Thread-safe, but all operations block the entire collection.
- Good for infrequent operations, but poor under high concurrency.
- Iteration requires separate synchronization on the object.
ConcurrentHashMap
- The map structure is safe for parallel reading and writing of different keys (lock-free at the bucket level).
- Race condition is possible only on mutable values if you just store objects without atomic operations.
- Solution: use
AtomicInteger,AtomicReferenceor methods likecompute,computeIfAbsent.
ConcurrentSkipListMap / ConcurrentSkipListSet
- The structure is correct, lock-free operations through CAS.
- Parallel changes to the same key are not atomic at the value level — atomicity is needed.
- Suitable if a sorted thread-safe set/map is required.
AtomicInteger / AtomicLong / AtomicReference
- Completely atomic operations, lock-free.
- No risk of losing updates, even if multiple threads change the value simultaneously.
- Often used together with ConcurrentHashMap for safe value updates.
🔹 Practical Rule
- If the collection is not thread-safe → either use external synchronization or replace it with a thread-safe version (ConcurrentHashMap, ConcurrentSkipListMap).
- If you store mutable objects inside the map → use atomics or compute methods, otherwise values may be lost.
- If maximum parallelism is needed → ConcurrentHashMap + AtomicInteger/Reference is the best option.
- If simple synchronization without hassle is needed →
Collections.synchronizedMap/Set— but remember about locking the entire collection.
Оставить комментарий
My social media channel
Useful Articles:
← Part 1 — Basics of Concurrency in Go for Java Developers In the second part, we will dive into synchronization and safety of concurrent code in Go. For a Java developer, it is useful to see t...
Java was originally designed for multithreading and parallel computing. Over time, various methods for working with the results of asynchronous tasks have emerged—from the classic Future to modern Str...
Imagine a typical production service. 32 CPU hundreds of threads configuration / session / rate limits cache tens of thousands of operations per second And somewhere inside — a regular Map. At first...
New Articles:
Concurrency is not about “starting many threads”. It’s about agreements between them. Imagine a restaurant kitchen: — cooks (threads / goroutines) — orders (tasks) — and the main question: how do th...
Imagine a typical production service. 32 CPU hundreds of threads configuration / session / rate limits cache tens of thousands of operations per second And somewhere inside — a regular Map. At first...
Zero Allocation — is an approach to writing code in which no unnecessary objects are created in heap memory during runtime. The main idea: fewer objects → less GC → higher stability and performance. ...