Expert · Multithreading and concurrency · topic 44 of 60 · 25 min
Atomic variables and concurrent collections
Explanation
Atomic variables
An atomic variable performs read-modify-write operations as one indivisible step using special CPU instructions (compare-and-swap), without locks. It is the simplest fix for shared counters and statistics.
AtomicInteger requests = new AtomicInteger();
requests.incrementAndGet(); // safe from any number of threads
requests.addAndGet(5);
int now = requests.get();
AtomicLong totalPaise = new AtomicLong();
totalPaise.addAndGet(49_900);
requests.updateAndGet(n -> Math.min(n, 1000)); // atomic update with a functionConcurrentHashMap
A plain HashMap used by several threads can lose entries or even corrupt itself. ConcurrentHashMap is designed for many readers and writers at once. Its compound methods (merge, compute, computeIfAbsent, putIfAbsent) are atomic, so counting per key is safe.
ConcurrentHashMap<String, Integer> hitsByCity = new ConcurrentHashMap<>();
// called from many request threads at the same time:
hitsByCity.merge("Pune", 1, Integer::sum); // atomic per key
hitsByCity.computeIfAbsent("Goa", k -> 0);
// WRONG with any map shared between threads (check-then-act race):
// if (!map.containsKey(k)) map.put(k, 1); else map.put(k, map.get(k) + 1);Other concurrent collections
| Class | Use when |
|---|---|
| ConcurrentHashMap | A shared map with frequent reads and writes |
| CopyOnWriteArrayList | A list read very often, changed rarely (listeners, configuration) |
| ArrayBlockingQueue / LinkedBlockingQueue | Producer-consumer hand-off between threads |
| ConcurrentLinkedQueue | A non-blocking shared queue |
| Collections.synchronizedList | Old style: one lock for everything; usually slower |
Producer-consumer with BlockingQueue
BlockingQueue<String> smsQueue = new ArrayBlockingQueue<>(100);
Thread producer = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try { smsQueue.put("OTP for user " + i); } catch (InterruptedException e) { return; } // waits if full
}
});
Thread consumer = new Thread(() -> {
try {
while (true) System.out.println("Sending " + smsQueue.take()); // waits if empty
} catch (InterruptedException e) { /* stop */ }
});CountDownLatch
CountDownLatch ready = new CountDownLatch(3); // wait for 3 services
for (String svc : List.of("db", "cache", "payments")) {
new Thread(() -> { connect(svc); ready.countDown(); }).start();
}
ready.await(); // main waits until the count reaches 0
System.out.println("All services connected, accepting traffic");Immutability: the simplest thread safety
An object that never changes (final fields, no setters: records, String, LocalDate, List.of) can be shared by any number of threads with no locks at all. Designing most data as immutable and keeping mutable shared state small is the most reliable concurrency strategy.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Atomic variables and concurrent collections · 1/6
In this lesson you will use ready-made thread-safe tools from java dot util dot concurrent.
Prefer a video by a teacher? Find free videos on “java atomicinteger concurrenthashmap blockingqueue countdownlatch” on YouTube ↗
In real life: Live request counters in an API gateway
An API gateway in front of a payments service counts requests per merchant to enforce rate limits. Hundreds of threads update the counts at once. A ConcurrentHashMap<String, AtomicInteger> with computeIfAbsent gives exact, lock-free counting per merchant.
ConcurrentHashMap<String, AtomicInteger> perMerchant = new ConcurrentHashMap<>();
boolean allow(String merchantId) {
int n = perMerchant.computeIfAbsent(merchantId, k -> new AtomicInteger()).incrementAndGet();
return n <= 100; // max 100 requests in the current window
}Practice (0/3 done)
Which correctly counts per key from many threads?
BlockingQueue.take() on an empty queue…
Build a producer-consumer program: one producer thread puts 20 "orders" into an ArrayBlockingQueue of capacity 5, and two consumer threads take and "process" them (sleep 50 ms each), updating an AtomicInteger of processed orders. Use a CountDownLatch(20) so main can print "All 20 processed" at the end.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Virtual threads (Java 21)Multithreading and concurrency · same module
- Concurrency 2: atomics, async, futures and condition variablesC++ Programming (modern C++ and DSA) · Exceptions, concurrency and building projects with CMake
- Message passing with channelsRust Programming · Concurrency and async
- Mutex, Once, atomic and the race detectorGo Programming (backend and cloud) · Concurrency: goroutines, channels and context
- Shared state: Arc, Mutex, Send and SyncRust Programming · Concurrency and async
- Pure functions and immutabilityJavaScript Programming (language in depth) · Functional patterns and testing
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What does AtomicInteger.incrementAndGet() return?
Answer: The new value
getAndIncrement returns the old value.
CopyOnWriteArrayList is best when…
Answer: Reads are frequent and writes are rare
Every write copies the array.
Why are immutable objects thread-safe?
Answer: Their state never changes, so there is nothing to race on
No writes, no races.
What does CountDownLatch.await() do?
Answer: Waits until the count reaches zero
Other threads call countDown().
Two bank balances must be updated together by several threads. Which tool?Related
Answer: One std::mutex locked around both updates
Only a lock makes the combined change appear all at once.
From Concurrency 2: atomics, async, futures and condition variables
How do several threads send on the same channel?Related
Answer: Clone the Sender for each thread
Sender can be cloned; each clone can move into a thread.
Which is best for a simple hit counter shared by many goroutines?Related
Answer: atomic.Int64
atomic.Int64 gives safe, fast Add and Load.
Many threads read a config and rarely update it. Which is a good fit?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Multithreading and concurrency” before its exam: Virtual threads (Java 21).
- Module 9 exam: Multithreading and concurrency · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, JDBC, Maven, Gradle and JUnit. 60 topics left in the course before the final exam.