How Java works

Pick a topic, write code and watch what happens inside, step by step.

Memory

Collections

Concurrency

Language and JVM

Topics

Threads

Race condition: count++

Each thread has its own stack, while the heap is shared. count is on the heap and r is on the stack.

count++ is not one operation: read, add, write. If two threads read the same value, one overwrites the result of the other.

Next plays a prepared order. Use the thread buttons to choose your own: which orders lose nothing?

synchronized

synchronized takes the monitor lock of an object. Only one thread can hold it at a time, and the other becomes BLOCKED.

Now try to run thread B in the middle of A: B cannot advance, so the three steps run together.

A lock also gives visibility: a thread taking the lock sees what the previous owner wrote. The price is waiting, so hold locks as briefly as you can.

volatile does not fix count++

volatile makes every read see the latest write. But read, add and write are still three separate steps.

See for yourself: the same order loses the same update. volatile gives no atomicity.

volatile fits when one thread writes and others read, like the running flag in a later section.

CAS and AtomicInteger

AtomicInteger uses no lock. CAS, compare-and-swap, is a single hardware step: if the value is what I expect, write the new one, otherwise do nothing.

A thread that fails does not wait. It reads again and retries, so threads do not become BLOCKED.

Under heavy contention it can retry a lot. For counters with many writes there is also LongAdder.

Deadlock

Thread A takes a and B takes b. Then A wants b and B wants a. Both wait forever, and Java does not resolve it.

The simplest fix: all threads take locks in the same order. Change the order with the button and run it again.

In a real program jstack finds and reports the deadlock.

Visibility: a volatile flag

When does one thread see what another wrote? Without synchronization, Java makes no promise.

In this loop the worker reads running. The JIT may assume the value does not change and skip re-reading it, so the thread spins forever.

volatile, synchronized and Thread.start/join create happens-before, which guarantees visibility.

Thread states

Thread.getState() returns one of six values. This table is what you need most when reading a thread dump.

Trap: RUNNABLE does not mean "on a CPU right now". A thread waiting on I/O often shows as RUNNABLE too.