Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
87: Concurrent Collections: ConcurrentHashMap
I've seen this exact bug pop up in production more times than I care to admit. A developer builds a feature that works perfectly in their local environment with a single user, but the second it hits a multi-threaded server environment, the data starts drifting or the application just crashes with a cryptic exception. Let's look at a scenario where we're trying to track page views for a small web server.
public class PageTracker {
private final Map<String, Integer> pageViews = new HashMap<>();
public void recordView(String page) {
int currentViews = pageViews.getOrDefault(page, 0);
pageViews.put(page, currentViews + 1);
}
public void printReport() {
for (Map.Entry<String, Integer> entry : pageViews.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
The Mystery of the Vanishing Page Hits
On the surface, recordView looks fine. But if you run this with ten threads hammering it simultaneously, you'll notice two things. First, your final counts will be lower than the actual number of calls—some increments just disappear. Second, if you call printReport while the other threads are still recording views, you'll likely get a ConcurrentModificationException. This happens because HashMap is not thread-safe; it doesn't expect the underlying structure to change while you're iterating over it.
Why the HashMap is Lying to You
The "vanishing hits" are caused by a race condition. The line pageViews.put(page, currentViews + 1) isn't a single atomic action. It's a "read-modify-write" sequence. Imagine Thread A and Thread B both read the current count for "/home" as 10. Both calculate 11. Thread A writes 11, then Thread B writes 11. We just had two views, but the count only went up by one. This is a classic synchronization failure.
You might be tempted to just wrap the HashMap in Collections.synchronizedMap(). I wouldn't recommend it for this. That approach locks the entire map for every single operation, which becomes a massive bottleneck as your traffic grows. It's like having a giant library where only one person is allowed inside at a time, regardless of which book they are looking for.
Making Updates Atomic with ConcurrentHashMap
This is where ConcurrentHashMap comes in. It doesn't lock the whole map; it uses a sophisticated striping mechanism (and CAS operations) to allow multiple threads to read and write to different parts of the map simultaneously. However, simply changing the type to ConcurrentHashMap won't fix the race condition in recordView. You're still doing a separate get and put.
To fix this, we use atomic methods like merge or compute. Here is how I would actually write this in a professional codebase:
public class PageTracker {
// Use ConcurrentHashMap for thread-safe access
private final Map<String, Integer> pageViews = new ConcurrentHashMap<>();
public void recordView(String page) {
// merge is atomic: it handles the "get, increment, put" in one go
pageViews.merge(page, 1, Integer::sum);
}
public void printReport() {
// ConcurrentHashMap's iterators are weakly consistent;
// they won't throw ConcurrentModificationException
pageViews.forEach((page, count) -> {
System.out.println(page + ": " + count);
});
}
}
The merge method is the secret sauce here. It says: "If the key page isn't there, put 1. If it is there, use the provided function (Integer::sum) to combine the old value with 1." Because ConcurrentHashMap guarantees this operation is atomic per key, no updates are lost, and we don't need to manually synchronize the whole method. Also, notice that forEach (and the entry set iterator) in a ConcurrentHashMap is designed to be used while the map is being modified, so those crashes disappear.
📋 Practical Task
Build a Thread-Safe User Session Cache
You are building a session management system where multiple request-handling threads update the "last activity" timestamp for users. A standard HashMap is causing crashes and data loss under load.
Your Task:
- Create a class
SessionCachethat uses aConcurrentHashMapto storeuserId(String) andlastActiveTimestamp(Long). - Implement a method
updateActivity(String userId)` that updates the timestamp toSystem.currentTimeMillis(). - Implement a method
getSessionDuration(String userId, long currentTimestamp)` that returns the difference between the current timestamp and the stored timestamp. If the user isn't in the cache, return-1. - Ensure that the
updateActivitymethod handles the insertion and update of the timestamp atomically. - Write a small
mainmethod that spawns 5 threads, each updating the same user ID 1,000 times, and verify that the program completes without throwing anyConcurrentModificationExceptionwhile you simultaneously print the cache contents in a separate thread.
There are no comments for now.