Question 21 · Section 4

When to use synchronized collections?

Imagine a toilet with a single key — while one thread is inside, all others wait in line, even if they just want to check if it's free.

Language versions: English Russian Ukrainian

🟢 Junior Level

Synchronized collections are regular collections with a lock on every method.

Imagine a toilet with a single key — while one thread is inside, all others wait in line, even if they just want to check if it’s free.

List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());

Problem: Only ONE thread can work at a time (even for reading!).

Modern alternative:

// ❌ Old approach (slow)
List<String> list = Collections.synchronizedList(new ArrayList<>());

// ✅ New approach (fast)
Map<String, Integer> map = new ConcurrentHashMap<>();
List<String> list = new CopyOnWriteArrayList<>();

synchronizedList — one lock for ALL operations, including reads. ConcurrentHashMap — lock-free reads and granular locks on writes, so N threads can read in parallel.


🟡 Middle Level

Problem: one lock for everything

// 100 threads want to read
syncList.get(0);  // All wait in line!
// → 0 parallelism

Compound operations are NOT atomic

// ❌ Race condition!
if (!syncList.contains(item)) {  // Thread A checked
    // Thread B slipped in and added
    syncList.add(item);  // Thread A added a duplicate!
}

// ✅ External synchronized
synchronized (syncList) {
    if (!syncList.contains(item)) {
        syncList.add(item);
    }
}

Iteration requires manual locking

// ❌ ConcurrentModificationException!
for (String s : syncList) { ... }

// ✅ Correct:
synchronized (syncList) {
    for (String s : syncList) { ... }
}

When to use

Scenario What to choose
New code ConcurrentHashMap
Listener lists CopyOnWriteArrayList
Full lock needed synchronizedList
Legacy code synchronizedList

COWAL is good for listeners: iteration (traversing all listeners) is more frequent than adding a listener. COWAL provides CME-free iteration and O(1) addition.


🔴 Senior Level

When NOT to use synchronized collections

  1. High contention (>10 threads) — ConcurrentHashMap will give an order of magnitude better throughput
  2. Frequent iterations — each requires manual synchronized(list) { ... }
  3. Compound operations (check-then-act) — still need external synchronized, so why use the wrapper?

Coarse-grained locking

One monitor for the ENTIRE collection:
  → 100 readers → serialization
  → On multi-core CPUs → 0 scalability

ConcurrentHashMap:
  → Lock-free reads
  → CAS + synchronized per bucket
  → 100 threads work in parallel

Double synchronization

// ❌ Double locking!
synchronized (syncList) {
    syncList.add(e);  // Internal synchronized + external
}

// synchronizedList already synchronizes every method
// → Extra overhead

Exposing the original collection

// ❌ Thread-safety violation!
List<String> original = new ArrayList<>();
List<String> sync = Collections.synchronizedList(original);

// Modification bypassing the wrapper:
original.add("x");  // NOT synchronized!

When synchronized collections are justified

// 1. Legacy systems (pre-Java 5 code)

// 2. Coarse-grained atomicity of the entire collection
synchronized (syncList) {
    // Guaranteed nobody can slip in
    for (var item : syncList) {
        process(item);
    }
}

// 3. Low contention + small size
// → Wrapper is cheaper than ConcurrentHashMap

Production Experience

Real scenario: synchronizedMap killed throughput

  • API: 1000 RPS, synchronizedMap for cache
  • Lock contention: 90% of time on synchronized
  • Solution: ConcurrentHashMap
  • Result: +900% throughput

Best Practices

  1. DO NOT use in new code
  2. ConcurrentHashMap — by default
  3. CopyOnWriteArrayList — for listeners
  4. Iteration → synchronized block is mandatory
  5. Compound operations → external synchronized
  6. Do not expose the original collection
  7. Vector/Hashtable → avoid in new code. The only case where they’re tolerable is single-threaded legacy code where replacement isn’t justified by the risks.

Summary for Senior

  • Single lock → 0 scalability
  • Compound operations → race condition without external synchronized
  • Iteration → manual synchronized block
  • ConcurrentHashMap > synchronizedMap in 99% of cases
  • Legacy → the only justification
  • Vector/Hashtable — built-in synchronized → legacy

🎯 Interview Cheat Sheet

Must know:

  • Synchronized collections — one lock for all methods (reads + writes)
  • Compound operations (check-then-act) are NOT atomic — need external synchronized
  • Iteration requires a manual synchronized(list) { ... } block
  • ConcurrentHashMap gives lock-free reads and granular locks on writes
  • CopyOnWriteArrayList is ideal for listener lists (frequent reads, rare writes)
  • Synchronized collections are not recommended in new code
  • Exposing the original collection breaks thread-safety
  • Vector/Hashtable — legacy, avoid in new code

Frequent follow-up questions:

  • Why does synchronizedMap kill throughput at 1000 RPS? — 90% of time is spent on lock contention; ConcurrentHashMap solves the problem.
  • Is the construct if (!list.contains(x)) list.add(x) atomic? — No, it’s two separate calls; an external synchronized(list) is needed.
  • Why would iterating over a synchronizedList without a synchronized block throw CME? — Iteration is a compound operation (hasNext + next), not protected by a single method call.
  • When are synchronized collections justified? — Legacy code pre-Java 5, low contention, coarse-grained atomicity of the entire collection.

Red flags (DO NOT say):

  • “synchronizedList is a good choice for new code” — no, ConcurrentHashMap/CopyOnWriteArrayList are better
  • “Compound operations are atomic in synchronizedList” — no, external synchronized blocks are needed
  • “Vector is a normal choice” — it’s a legacy class with coarse-grained locking
  • “synchronizedList solves all concurrency problems” — it doesn’t solve race conditions for compound operations

Related topics:

  • [[22. How to get a synchronized collection]]
  • [[26. What are fail-fast and fail-safe iterators]]
  • [[27. What is ConcurrentModificationException]]