Count Words Concurrently Using ConcurrentHashMap in Java
Problem
ConcurrentHashMap lets many threads read and update a map at the same time, and its merge method updates a single key atomically.
Split a text into three chunks, count the words in each chunk on its own thread, and combine the counts into one map.
Java Program
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentWordCount {
public static void main(String[] args) throws InterruptedException {
String[] chunks = {
"rain falls on the hill",
"the hill drains to the river",
"the river reaches the sea"
};
ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
Thread[] workers = new Thread[chunks.length];
for (int i = 0; i < chunks.length; i++) {
final String chunk = chunks[i];
workers[i] = new Thread(() -> {
for (String word : chunk.split(" ")) {
counts.merge(word, 1, Integer::sum); // atomic read-modify-write per key
}
});
workers[i].start();
}
for (Thread w : workers) w.join();
Map<String, Integer> sorted = new TreeMap<>(counts);
System.out.println(sorted);
}
}Output
Core Logic
All threads write into one ConcurrentHashMap, and merge(word, 1, Integer::sum) performs each increment as a single atomic step.
- 1Each thread splits its chunk into words.
- 2
counts.merge(word, 1, Integer::sum)inserts 1 or adds 1 to the existing count, atomically. - 3Main joins every thread before reading the map.
- 4The counts are copied into a
TreeMapto print them sorted.
Key Point: map.put(word, map.get(word) + 1) is not safe even on a ConcurrentHashMap — the get and the put are separate steps another thread can slip between.
Key Concepts
Approach 2: Using a Parallel Stream
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
public class ConcurrentWordCountParallel {
public static void main(String[] args) {
String[] chunks = {"rain falls on the hill", "the hill drains to the river", "the river reaches the sea"};
ConcurrentMap<String, Long> counts = Arrays.stream(chunks)
.parallel()
.flatMap(c -> Arrays.stream(c.split(" ")))
.collect(Collectors.groupingByConcurrent(w -> w, Collectors.counting())); // safe in parallel
Map<String, Long> sorted = new TreeMap<>(counts);
System.out.println(sorted);
}
}
Output
Core Logic
A parallel stream can do the same splitting and counting, with groupingByConcurrent collecting into a concurrent map.
- 1
Arrays.stream(chunks).parallel()spreads the chunks across threads. - 2
flatMapturns each chunk into a stream of words. - 3
groupingByConcurrent(w -> w, counting())counts them safely from several threads.
Key Point: Let the stream framework manage threads when it can — it handles splitting, joining and thread pooling for you.
Key Concepts
Interview Questions
Why does ConcurrentHashMap reject null keys and values?
A get returning null would be ambiguous — "absent" or "mapped to null" — and in concurrent code you can't safely check containsKey afterwards.
How is ConcurrentHashMap different from Collections.synchronizedMap?
synchronizedMap locks the whole map for every operation, while ConcurrentHashMap allows concurrent reads and fine-grained concurrent updates.