Java Programs›Multithreading›Count Words Concurrently Using ConcurrentHashMap

Count Words Concurrently Using ConcurrentHashMap in Java

advanced·  Multithreading  ·  Concurrency

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.

Input
3 text chunks
Output
{drains=1, falls=1, hill=2, ..., the=5, to=1}

Java Program

Java
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

{drains=1, falls=1, hill=2, on=1, rain=1, reaches=1, river=2, sea=1, the=5, to=1}

Core Logic

All threads write into one ConcurrentHashMap, and merge(word, 1, Integer::sum) performs each increment as a single atomic step.

How It Works
  1. 1Each thread splits its chunk into words.
  2. 2counts.merge(word, 1, Integer::sum) inserts 1 or adds 1 to the existing count, atomically.
  3. 3Main joins every thread before reading the map.
  4. 4The counts are copied into a TreeMap to print them sorted.
"the" appears five times across the three chunks (once, twice and twice), so its final count is 5.
💡

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

ConcurrentHashMapmerge()thread-per-chunkTreeMap for sorted output

Approach 2: Using a Parallel Stream

Java
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

{drains=1, falls=1, hill=2, on=1, rain=1, reaches=1, river=2, sea=1, the=5, to=1}

Core Logic

A parallel stream can do the same splitting and counting, with groupingByConcurrent collecting into a concurrent map.

How It Works
  1. 1Arrays.stream(chunks).parallel() spreads the chunks across threads.
  2. 2flatMap turns each chunk into a stream of words.
  3. 3groupingByConcurrent(w -> w, counting()) counts them safely from several threads.
The result matches the hand-written threaded version exactly.
💡

Key Point: Let the stream framework manage threads when it can — it handles splitting, joining and thread pooling for you.

Key Concepts

parallel()flatMap()Collectors.groupingByConcurrent()

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.

Learn the Concept

Related Programs