Table of Contents:
- 1️⃣ HashMap / TreeMap / TreeSet (not thread-safe)
- 2️⃣ SynchronizedMap / SynchronizedSortedMap / SynchronizedSortedSet
- 3️⃣ ConcurrentHashMap
- 4️⃣ ConcurrentSkipListMap / ConcurrentSkipListSet
- 5️⃣ AtomicInteger / AtomicReference / AtomicLong
- Comparison tables: one element, two threads
- Table: one element edited by two threads + a new key added
- Comparison of Thread-Safe Sorted Collections
- 🔹 Conclusions
- 🔹 Rule of Thumb
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: put/remove modifies the bucket array and possibly reorders the chains.
- Problem with multithreading: two threads can simultaneously modify the same bucket → corrupt structure, element loss, infinite loop during iteration.
- Structure: red-black tree.
- When inserting/deleting, a thread modifies multiple tree nodes.
- Concurrent modifications → tree structure can If get/put/rebalance breaks, they can throw a ConcurrentModificationException or corrupt the balance.
✅ Conclusion: External synchronization is needed, otherwise the map/set is easily 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 for the duration of the operation. This guarantees data integrity, but prevents parallelism.
public V put(K key, V value) {
synchronized (this) {
return m.put(key, value);
}
}
Read/write → one operation at a time, eliminating race conditions.
3️⃣ ConcurrentHashMap
Under the hood (Java 8+):
- Array of buckets + tree nodes for collisions.
- Primary synchronization:
- get — completely lock-free.
- put — CAS (Compare-And-Swap) at the bucket level or synchronized on the tree node only if needed.
- Table expansion (resize) is partially synchronized on segments.
- Concurrent put/get operations on different keys are performed in parallel without locking the entire map.
- Race condition On value: If this is a mutable object within 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 list level is partially locked or updated atomically.
- Allows parallel operations on different keys.
- Simultaneous changes to one key → race condition unless atomic methods are used (compute, putIfAbsent).
5️⃣ AtomicInteger / AtomicReference / AtomicLong
- Use CAS CPU instructions (Compare-And-Swap).
- Atomic and lock-free updates: the same value won't be lost across concurrent threads.
- Example:
AtomicInteger ai = new AtomicInteger(0); ai.incrementAndGet(); // atomic - No JVM- or object-level locks – pure CAS on the CPU.
Comparison tables: 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 break structure, data loss | No synchronization, everything is 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 is waiting | ✅ safe, one thread is waiting for another | The monitor locks the entire map/set for the operation |
| ConcurrentHashMap | ⚠️ map structure is OK, but race condition: last assignment wins | ⚠️ CAS at the bucket or node level → last assignment wins | Structure Maps are lock-free, the object field is not protected. |
| ConcurrentSkipListMap / Set | ⚠️ Skip list structure is valid, value race condition exists. | ⚠️ Last assignment wins. | Node-level CAS, value object is not atomic. |
| AtomicInteger / AtomicReference / AtomicLong | ✅ Atomic, thread 2 waits in CAS only if there is a conflict, value is incremented correctly. | ✅ Atomic, CAS guarantees one update → the second one retries. | CPU CAS, lock-free, race condition excluded. |
Table: one element edited by two threads + a new key added
| Collection / Object | Code Example | Threads 1+2 edit an element | Thread 3 adds a 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 corruption |
| TreeMap / TreeSet | |
❌ race condition, tree structure may break | ❌ adding a new key changes tree nodes | No thread safety, tree balancing breaks | High risk of corruption |
| SynchronizedMap / SynchronizedSortedMap / SynchronizedSortedSet | |
✅ Threads 1 and 2 execute sequentially (lock on monitor) | ✅ Thread 3 waits for completion | All methods are synchronized on the object, iteration requires a block | Low, safe |
| ConcurrentHashMap | |
⚠️ Race condition on value, map structure is correct | ✅ Adding a new key in parallel | Lock-free buckets + CAS, lock-free reads | Value may be lost, structure is fine |
| ConcurrentSkipListMap / Set | |
⚠️ Race condition on value | ✅ Adding a new key in parallel | Skip list + CAS, lock-free | Value may be lost, structure is fine |
| AtomicInteger / AtomicReference | |
✅ Atomic, both threads update the value correctly | ✅ Adding a new key through a separate AtomicReference object is safe | CPU-level CAS, lock-free | Low, safe |
🔹 Key points:
- SynchronizedMap — methods lock the entire object, so Thread 1 and Thread 2 never execute concurrently.
- ConcurrentHashMap — the map structure is safe, but the object's value is not atomic, so an AtomicInteger or the
computemethod is required. - Atomic objects are completely atomic, eliminating race conditions.
Comparison of Thread-Safe Sorted Collections
1️⃣ Collections.synchronizedSortedMap(new TreeMap<<())< /div>
Collections.synchronizedSortedMap(new TreeMap<<())
Thread A: ─────[took monitor]───────────────> Running
Thread B: ─────[waiting, monitor busy]─────┐
Thread C: ─────[waiting, monitor busy]─────┘
All operations block each other: get, put, remove, containsKey — only one thread can execute within the same thread at a time.
Output:
The 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 performed concurrently because locks are only taken on small segments of the list.
✔ Key order is preserved, submaps work.
Output:
Not a tree, but a skip list.
Sorted keys like in TreeMap.
Multithreaded, parallel, without global locking.
| Property | SynchronizedSortedMap(TreeMap) | ConcurrentSkipListMap |
|---|---|---|
| Structure | Red-black tree | Skip list |
| Key ordering | Yes | Yes |
| Thread safety | Yes, via synchronized | Yes, via fine-grained locks |
| Parallel operation | No, single thread | Yes, multiple threads simultaneously |
| SubMap/headMap support | Yes | Yes |
| Performance with many threads | Drops | Good |
🔹 Conclusions
HashMap / TreeMap / TreeSet
- Not thread-safe.
- Any concurrent changes can lead to data loss, structure corruption, and infinite iterations.
- Requires external synchronization (e.g., a
synchronizedblock).
SynchronizedMap / SynchronizedSortedMap / SynchronizedSortedSet
- Thread-safe, but all operations lock the entire collection.
- Good for infrequent operations, but poor for high concurrency.
- Iteration requires separate synchronization on the object.
ConcurrentHashMap
- The map structure is safe for concurrent reading and writing of different keys (lock-free at the bucket level).
- Race conditions are only possible on mutable values if you are simply storing objects without atomic operations.
- Solution: use
AtomicInteger,AtomicReference, or methods likecompute,computeIfAbsent.
ConcurrentSkipListMap / ConcurrentSkipListSet
- The structure is correct, operations are lock-free via CAS.
- Concurrent changes to the same key are not atomic at the value level – atomics are required.
- Suitable if you need a sorted map Thread-safe set/map.
AtomicInteger / AtomicLong / AtomicReference
- Fully atomic operations, lock-free.
- No risk of losing updates, even if multiple threads simultaneously modify the value.
- Often used in conjunction with ConcurrentHashMap to safely update values.
🔹 Rule of Thumb
- 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 a map → use atomics or compute methods, otherwise values may be lost.
- If you need maximum concurrency → ConcurrentHashMap + AtomicInteger/Reference is the best option.
- If you need simple synchronization without any hassle, →
Collections.synchronizedMap/Set— but remember to lock the entire collection.
My social media channel
Useful Articles:
In modern Java development, there are three main approaches to asynchrony and concurrency: CompletableFuture — for single asynchronous tasks. Flow / Reactive Streams — for data flows with backpressur...
1️⃣ HashMap / TreeMap / TreeSet (not thread-safe) HashMap: Structure: array of buckets + linked lists / trees (for collisions). Under the hood: put/remove modifies the bucket array and possibly reord...
Atomic operations Atomic operations ensure correct execution of variable operations without race conditions, guaranteeing a happens-before between reads and writes. Go example: import "sync/atomic" va...
New Articles:
In this article we will analyze advanced type system features in Go: generics (type parameters), reflection, and channel types for concurrency. We will compare Go and Java approaches, so Java develope...
Series: Go for Java Developers — analysis of trace, profiling and testing In this article we will analyze tools and practices for testing, debugging and profiling in Go. For a Java developer this wil...
This article is dedicated to understanding the principles of concurrency and synchronization in Go and Java. We ll cover key approaches such as rate-limiter, non-blocking operations, and task scheduli...