When two thread-safe actions combined create a thread-unsafe nightmare.
Using a "Thread-Safe" data structure like a ConcurrentHashMap means individual calls like get() or put() are safe. But if you combine them—e.g., calling get(), doing some math, and then calling put()—the sequence as a whole is not safe. Between your get() and your put(), another thread can jump in and change the map! This is a compound operation race condition.
You cannot fix this by putting a lock around the get() and the put(), because that defeats the entire purpose of a fast, lock-free concurrent map! Instead, you must use the special built-in atomic compound methods provided by the data structure, like compute() or putIfAbsent().
# The Bug (Missing Lock on Compound Operation)
# map is a thread-safe ConcurrentHashMap
val = map.get("visits") // Thread A reads 1
# Thread B reads 1, writes 2!
map.put("visits", val + 1) // Thread A writes 2. We lost a visit!
# The Fix (Use Atomic Compound Methods)
# The Map handles the read-modify-write as one indivisible step.
map.compute("visits", (key, oldVal) -> {
return oldVal == null ? 1 : oldVal + 1;
});
Using compute() usually involves passing a lambda function. Behind the scenes, the data structure uses optimistic locking (Compare-And-Swap). If two threads collide, one of them will automatically retry the lambda function. Therefore, the lambda must be fast and have no side effects (pure function).
ConcurrentHashSet or use a database Unique Constraint.