Concurrency bugs are difficult because correctness depends on timing. I experimented with three Java building blocks: conditional updates in ConcurrentHashMap, coordination with CountDownLatch, and the failure mode that occurs when a latch is never released.
1. Avoid check-then-act updates
A thread-safe map does not make a sequence of separate operations atomic. This pattern has a race: another thread can change the value after get() and before put().
|
|
ConcurrentHashMap provides a conditional replace operation. It updates the mapping only if the current value matches the expected value, and returns whether the replacement happened. This is CAS-like at the API level; it is safer to describe it as an atomic conditional update rather than assume every call maps to one specific hardware instruction.
|
|
2. start(), join() and CountDownLatch solve different problems
Calling t1.start() and then t2.start() starts two independent threads; it does not guarantee which worker runs first. Calling t1.join() makes the calling thread wait for t1 to finish. It does not make t2 wait for t1.
Use a CountDownLatch when one thread must wait for an event signalled by another thread. In this example, worker B waits until worker A has completed its update and decremented the latch.
|
|
Actions in a thread before countDown() happen-before actions following a corresponding successful await() in another thread. The latch is a one-shot coordination mechanism; it does not make unrelated shared state thread-safe by itself.
3. Complete example
|
|
Worker A changes Cabbage to Charlie. After the latch opens, worker B tries to replace Cabbage; that conditional update returns false because the value is now Charlie.
4. wait() vs sleep() and latch timeouts
Thread.sleep()pauses the current thread without releasing any monitor it owns.Object.wait()must be called while holding that object’s monitor. It releases that monitor while waiting and can be resumed by notification, interruption or timeout.- If a worker throws before calling
countDown(), another worker can wait forever. Put the countdown in afinallyblock when completion should always release the latch, and considerawait(timeout, unit)when an indefinite wait is unacceptable.
What I took away
Use atomic map operations for compound updates, use CountDownLatch to signal a one-time event, and make failure paths part of the concurrency design. Starting threads does not establish execution order.