Java's concurrency model is large. Most of it is never needed. Here is the part that comes up repeatedly in real production systems.

The memory model in one sentence

The Java Memory Model guarantees that if thread A writes a value and thread B reads it, B will see A's write only if there is a happens-before relationship between the write and the read.

Without that relationship, B may see a stale cached value. synchronized, volatile, and the java.util.concurrent locks all establish happens-before.

volatile

volatile ensures a write is immediately visible to other threads. It does not provide atomicity.

private volatile boolean running = true;

// Thread A
running = false;

// Thread B — will eventually see false because of volatile
while (running) { doWork(); }

Use volatile for single-field flags. Use AtomicBoolean if you need compare-and-set.

synchronized

synchronized provides both mutual exclusion and happens-before. Every synchronized block is a happens-before boundary.

Keep synchronized blocks short. Hold locks only long enough to protect the critical section. The longer a lock is held, the more contention.

ReentrantLock

ReentrantLock does everything synchronized does, with extras:

Use ReentrantLock when you need these features. Use synchronized otherwise — it is simpler and the JVM optimizes it well.

Executors over raw threads

Creating threads directly is rarely the right choice. The ExecutorService API manages thread pools, handles lifecycle, and separates task submission from execution policy.

ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> doWork());
pool.shutdown();

For Java 21+, consider virtual threads for I/O-heavy workloads. They are cheap enough to create per-task.

Deadlock

Deadlocks are caused by circular lock acquisition. If you must acquire multiple locks, always acquire them in the same order everywhere. tryLock with a timeout is a practical fallback.

The one rule

If a field is accessed by more than one thread, something must provide visibility. If it can be written by more than one thread, something must provide atomicity. Pick the right tool based on what you actually need.