Concurrent Collections
Thread-safe collection — ConcurrentHashMap, CopyOnWriteArrayList, BlockingQueue
1. Why Not Just synchronized a HashMap?
You could wrap a HashMap with Collections.synchronizedMap, but every single
operation then grabs one global lock — turning a hot data structure into a bottleneck. Worse,
HashMap itself is not safe for concurrent writes even to different keys — you can
corrupt its internal arrays. java.util.concurrent gives you purpose-built collections that
are fast and safe.
Collections.synchronizedMap দিয়ে wrap করলেও পুরো map-এর উপর একটি global lock পড়ে — bottleneck। আর HashMap-এ আলাদা key-তে concurrent write-ই internal array corrupt করতে পারে। java.util.concurrent package দ্রুত এবং নিরাপদ — দুই-ই।
Collections.synchronizedX should almost never appear in new code.
2. ConcurrentHashMap
The flagship. Internally divided into segments with separate locks (historically) or lock-free tree bins
(Java 8+). Multiple threads can read and write concurrently without contending on a single lock.
getOrDefault, computeIfAbsent, merge, and forEach are all
atomic at the entry level.
computeIfAbsent, merge, forEach সব entry-level atomic।
import java.util.*;
import java.util.concurrent.*;
class Main {
public static void main(String[] args) throws Exception {
ConcurrentHashMap<String, Integer> hits = new ConcurrentHashMap<>();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
hits.merge("home", 1, Integer::sum);
}
};
Thread a = new Thread(task), b = new Thread(task);
a.start(); b.start();
a.join(); b.join();
System.out.println("home hits = " + hits.get("home")); // 2000
}
}
3. CopyOnWriteArrayList
Every mutation copies the entire backing array. That is expensive for writes and free for reads — iterators never see a mutation mid-flight. Use it when reads vastly outnumber writes: event listeners, rarely-changing config.
import java.util.concurrent.*;
class Main {
public static void main(String[] args) {
CopyOnWriteArrayList<String> listeners = new CopyOnWriteArrayList<>();
listeners.add("metrics");
listeners.add("audit");
// iteration is safe even if another thread adds during the loop
for (String l : listeners) {
System.out.println("notify " + l);
listeners.add("logger"); // no ConcurrentModificationException
}
System.out.println("final = " + listeners);
}
}
4. BlockingQueue — The Producer/Consumer Pattern
A BlockingQueue is the textbook channel between threads. Producers call put (blocks
if full), consumers call take (blocks if empty). Common implementations:
ArrayBlockingQueue (bounded), LinkedBlockingQueue (optionally bounded).
BlockingQueue — thread-এর মধ্যে channel। producer put (full হলে block), consumer take (empty হলে block)। ArrayBlockingQueue (bounded), LinkedBlockingQueue (optionally bounded)।
import java.util.concurrent.*;
class Main {
public static void main(String[] args) throws Exception {
BlockingQueue<Integer> q = new ArrayBlockingQueue<>(3);
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
q.put(i);
System.out.println("put " + i);
}
q.put(-1); // sentinel
} catch (InterruptedException e) {}
});
Thread consumer = new Thread(() -> {
try {
while (true) {
int v = q.take();
if (v == -1) break;
System.out.println("got " + v);
}
} catch (InterruptedException e) {}
});
producer.start(); consumer.start();
producer.join(); consumer.join();
}
}
5. ConcurrentLinkedQueue — Lock-Free
A non-blocking, lock-free queue based on CAS. It does not block when empty (poll returns
null) — useful when the consumer wants to do other work instead of waiting.
poll null ফেরত দেয়। consumer অপেক্ষার বদলে অন্য কাজ করতে চাইলে ভালো।
import java.util.concurrent.*;
class Main {
public static void main(String[] args) {
ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<>();
q.offer("a"); q.offer("b"); q.offer("c");
String v;
while ((v = q.poll()) != null) System.out.println(v);
System.out.println("empty: " + q.isEmpty());
}
}
6. Picking the Right Collection
| Need | Use | বাংলায় |
|---|---|---|
| Shared key-value cache | ConcurrentHashMap | shared key-value cache |
| Read-mostly list | CopyOnWriteArrayList | বেশি read কম write তালিকা |
| Bounded producer/consumer | ArrayBlockingQueue | bounded producer-consumer |
| Unbounded producer/consumer | LinkedBlockingQueue | unbounded producer-consumer |
| Lock-free FIFO | ConcurrentLinkedQueue | lock-free FIFO |
| Ordered set / map | ConcurrentSkipListMap / Set | sorted concurrent map/set |
| Delayed tasks | DelayQueue | নির্দিষ্ট delay-এর পর available |
✅ Do
- Use concurrent collections in multi-threaded code
- Prefer
computeIfAbsent/mergeover get+put - Bound your queues in production
⚠️ Avoid
Collections.synchronizedMapin hot paths- Compound read-modify-write with plain
HashMap - Unbounded queues — they hide producer/consumer imbalance
7. Vocabulary
| Term | Meaning | বাংলায় |
|---|---|---|
| ConcurrentHashMap | High-throughput thread-safe map. | high-throughput thread-safe map। |
| Copy-on-write | Each mutation copies the underlying array. | প্রতিটি mutation array কপি করে। |
| BlockingQueue | Queue where put/take can block on full/empty. | full/empty-তে block-able queue। |
| Lock-free | Uses CAS instead of mutex locks. | mutex-এর বদলে CAS। |
| Producer-consumer | Pattern where one thread produces items another consumes. | এক thread উৎপাদন, অন্যটি ব্যবহার। |
| Back-pressure | Slowing the producer when the consumer is behind. | consumer slow হলে producer-ও slow। |
8. Practice Problems
-
Count word frequencies in a list from two threads using
ConcurrentHashMap.merge.দুটি thread থেকে word frequency গুনতেConcurrentHashMap.mergeব্যবহার করুন।Show Answer (উত্তর দেখুন)
Main.javaimport java.util.*; import java.util.concurrent.*; class Main { public static void main(String[] args) throws Exception { ConcurrentHashMap<String, Integer> m = new ConcurrentHashMap<>(); List<String> words = List.of("a","b","a","c","b","a"); Runnable r = () -> words.forEach(w -> m.merge(w, 1, Integer::sum)); Thread t1 = new Thread(r), t2 = new Thread(r); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(m); } } -
Explain in 2 sentences when
CopyOnWriteArrayListis a good fit.দুই বাক্যে বলুন —CopyOnWriteArrayListকখন ভালো।Show Answer (উত্তর দেখুন)
Answer: It is ideal when reads vastly outnumber writes and iterations must never see mid-write state — event listener lists, rarely-updated configuration, observer registrations. The copy-per-write cost makes it a bad choice for any list that changes on every request.
read >> write এবং iteration-এ mid-write state না দেখতে হলে উপযুক্ত — event listener, config, observer list। প্রতিটি request-এ পাল্টায় এমন list-এ খারাপ।
-
Implement a producer/consumer using
LinkedBlockingQueuewith 3 items.LinkedBlockingQueueদিয়ে ৩টি item-এর producer/consumer বানান।Show Answer (উত্তর দেখুন)
Main.javaimport java.util.concurrent.*; class Main { public static void main(String[] args) throws Exception { BlockingQueue<String> q = new LinkedBlockingQueue<>(); Thread p = new Thread(() -> { try { q.put("rice"); q.put("oil"); q.put("salt"); q.put("STOP"); } catch (InterruptedException e) {} }); Thread c = new Thread(() -> { try { while (true) { String s = q.take(); if (s.equals("STOP")) break; System.out.println("consumed " + s); } } catch (InterruptedException e) {} }); p.start(); c.start(); p.join(); c.join(); } } -
Cache a computed value per key with
computeIfAbsent.computeIfAbsentদিয়ে key-ভিত্তিক cache বানান।Show Answer (উত্তর দেখুন)
Main.javaimport java.util.concurrent.*; class Main { public static void main(String[] args) { ConcurrentHashMap<Integer, Long> cache = new ConcurrentHashMap<>(); for (int i : new int[]{5, 10, 5, 10, 15}) { long v = cache.computeIfAbsent(i, k -> { System.out.println(" computing " + k); return (long) k * k; }); System.out.println(i + " -> " + v); } } } -
In 3 sentences, say why
Collections.synchronizedMapis a bottleneck compared toConcurrentHashMap.তিন বাক্যে বলুন —Collections.synchronizedMapকেনConcurrentHashMap-এর চেয়ে ধীর।Show Answer (উত্তর দেখুন)
Answer:
Collections.synchronizedMapwraps every operation — including pure reads — in a single lock on the whole map, so concurrent readers serialize one after another.ConcurrentHashMappartitions the data and uses fine-grained or lock-free updates per bucket, allowing many threads to operate in parallel without contention. On typical workloads the difference is an order of magnitude, and it grows with core count.Collections.synchronizedMapপ্রতিটি operation (pure read সহ) পুরো map-এর single lock-এ চালায় — concurrent reader-রাও serial হয়ে যায়।ConcurrentHashMapভেতরে partition + fine-grained/lock-free — অনেক thread parallel-এ কাজ করে। core বাড়ালে পার্থক্য আরো বাড়ে।
Summary — Module 40
For threaded code, reach for java.util.concurrent collections. ConcurrentHashMap
is the go-to shared map; CopyOnWriteArrayList is a read-mostly list;
BlockingQueue is the producer/consumer channel. They are faster and safer than wrapping plain
collections with Collections.synchronizedX.
java.util.concurrent collection ব্যবহার করুন। ConcurrentHashMap = shared map, CopyOnWriteArrayList = read-mostly list, BlockingQueue = producer/consumer channel। Collections.synchronizedX-এর চেয়ে দ্রুত ও নিরাপদ।