Binary SemaphoreBinary Semaphore/learn
mentorshiptopicsroadmapsnotebooks
sign in
CC++JavaPythonJavaScriptTypeScriptGoDSAOperating SystemsNetworksDatabasesSystem DesignTheory and CompilersMaths for CSFrontendBackend and APIsSecurityCloud and DevOpsTestingAIGit and the ShellDebuggingInterviews and Viva
Java
  • memory-and-gc
  • concurrency-traps
  • collections-in-anger
  • equals-and-hashcode
  • interview-questions
  • viva-defence

© 2026 Binary Semaphore

discord

Java

Concurrency traps

synchronized, volatile, and the race that only appears on the marker's machine.

Concurrency bugs are not hard because threads are hard. They are hard because the bug is invisible until it is not, and then only on a machine that is not yours.

Your laptop has fewer cores, a different scheduler and a warm cache. It will run the broken code correctly ten thousand times. The marker's machine will not.

Three problems wearing one name

"Thread safety" gets used for three separate things, and mixing them up is where most confusion starts.

Atomicity. count++ is three operations: read, add, write. Two threads can interleave and lose an update. Nothing about int prevents this.

Visibility. A thread can write a value and another thread can simply not see it, indefinitely. Not because the write was slow, but because there is no guarantee it was ever published. Each core has its own view.

Ordering. The compiler and the processor are both allowed to reorder your instructions, as long as a single thread cannot tell. Another thread can tell.

volatile fixes visibility and ordering. It does not fix atomicity, which is why volatile int count still loses increments. That single sentence is worth more than any diagram of the memory model.

synchronized, and what it actually gives you

A synchronized block does two things, and everyone remembers only the first.

It gives mutual exclusion: one thread in the block at a time.

It also establishes happens-before: everything the previous holder wrote is guaranteed visible to the next one. That is the half people forget, and it is why removing a lock "because it is only reading" is such a reliable way to make a program mysterious.

Two rules keep you out of the worst of it. Lock on a private final object, not on this and never on a String or a boxed , because those are shared in ways you did not intend. And hold the lock for the shortest useful stretch, but not so short that you have split one operation into two.

More on this is coming, and asking moves it up the list.

Want this written up?

Sign in and tell us. We write the ones people actually ask for, and asking takes one click.

Sign in to ask

Or sit with someone on it instead of waiting for us to write it.

What we have

Nothing yet, and no canonical source worth pointing at.

Book an hour on this
Integer

Deadlock, which is boring and preventable

Two threads, two locks, taken in opposite orders. That is it. That is the whole mechanism.

The fix is equally dull: always take locks in the same order. Give them an order, write it down, and stick to it. Almost every real deadlock is a violation of an ordering nobody ever agreed.

The traps that actually show up

Check-then-act. if (!map.containsKey(k)) map.put(k, v) is a race even on a ConcurrentHashMap, because two threads can both pass the check. Use putIfAbsent or computeIfAbsent. The concurrent collection makes each operation safe, not your pair of them.

Double-checked locking without volatile. The famous broken idiom. Another thread can see a non-null reference to an object that is not finished being constructed. volatile on the field is what makes it correct.

Mutating shared state from a stream. parallelStream() looks like free speed and quietly turns every shared variable into a race.

Catching InterruptedException and swallowing it. You have thrown away a cancellation signal. Either handle it or restore the flag with Thread.currentThread().interrupt().

Prefer not doing it yourself

Almost nothing you write needs raw threads. ExecutorService handles the pool. ConcurrentHashMap and the atomic classes handle the common shared structures. CompletableFuture handles composition.

The strongest move is still to have no shared mutable state. An immutable object cannot be raced. A queue between two threads is easier to reason about than a lock protecting a field they both touch.

Under questioning

Expect: what does volatile do, difference between a race and a deadlock, why is count++ not atomic, and how would you make this class thread safe.

The answer that separates people is not the definition. It is saying which of atomicity, visibility and ordering the problem in front of you actually is.