🎏 fucking-java-concurrency
📖 English Documentation | 📖 中文文档
Minimal demos of Java concurrency problems — seeing 🙈 is believing 🐵.
🍎 Why these demos
- An observed symptom 🙈 is more intuitive and convincing than a stated
concurrency principle 🙊.
- The
Java standard library supports threads, and multithreading is
heavily used both in the language itself (e.g. GC) and in
applications (server side).
- Concurrency greatly increases the complexity of program design,
analysis, and implementation. Unless you fully understand and
systematically analyze the concurrent logic, writing code at random
means it is no exaggeration to say that such a program runs
correctly by accident.
- The demos here come with no explanation or discussion, and all of
them are entry-level 🤓 . For more information, look
up concurrency material yourself.
Hit a concurrency problem in your own development? Please share it —
submit an issue
or fork the repo
and open a pull request! 😘
🍺 Unsynchronized updates are not visible to other threads
Demo class NoPublishDemo.
Demo description
The main thread sets the field stop to true to signal the task
thread (started in main) to exit.
Problem statement
After the main thread sets stop to true, the task thread keeps
running — i.e. it never sees the new value.
Quick run
./mvnw compile exec:java -Dexec.mainClass=fucking.concurrency.demo.NoPublishDemo
🍺 Infinite loop in HashMap
This problem has been explained in many places.
The Demo class HashMapHangDemo
can reproduce this problem.
Demo description
The main thread starts two task threads that put into the HashMap,
while itself repeatedly performing get.
Problem statement
The main thread blocks (output stops), i.e. the HashMap has entered
an infinite loop.
Quick run
./mvnw compile exec:java -Dexec.mainClass=fucking.concurrency.demo.HashMapHangDemo
🍺 Reading combined state yields an invalid combination
Programs often need to track several related pieces of state (a
POJO, a few ints, etc.).
Multi-state read/write code is frequently left unsynchronized, and
whoever writes it naturally overlooks thread safety.
An invalid combination is a combination of values that the program
never actually wrote.
Demo description
The main thread modifies multiple states. For convenience of checking,
every write keeps a fixed relationship: the second state is always
twice the first. A task thread reads the states.
Demo class InvalidCombinationStateDemo.
Problem statement
The task thread reads a second state that is not twice the first — a
combination that was never written.
Quick run
./mvnw compile exec:java -Dexec.mainClass=fucking.concurrency.demo.InvalidCombinationStateDemo
🍺 Reading a long variable yields an invalid value
An invalid value is one that was never written.
Reads and writes of non-volatile long and double are not guaranteed
to be atomic.
per the Java Language Specification (§17.7), a single
read/write of a 64-bit value may be treated as two separate 32-bit
accesses. On 32-bit JVMs this is necessarily split into two 4-byte
operations; on 64-bit JVMs it is typically done atomically in practice,
but the specification still does not guarantee atomicity.
Demo class InvalidLongDemo.
Demo description
The main thread modifies the long variable. For convenience of
checking, the upper and lower 4 bytes of every value written are
identical. A task thread reads the long.
Problem statement
The task thread reads a long whose upper and lower 4 bytes differ —
a value that was never written.
Quick run
./mvnw compile exec:java -Dexec.mainClass=fucking.concurrency.demo.InvalidLongDemo
🍺 Consecutive reads of the same field see different values
Demo class InconsistentReadDemo.
Demo description
The main thread increments the plain count field in a tight loop.
A task thread performs two consecutive reads of count and reports
each time the two values differ.
Problem statement
Two consecutive reads of the same non-volatile field by the same
thread return different values. For a plain field, each read may
observe a different write, so a thread must not assume that two reads
see the same value.
Quick run
./mvnw compile exec:java -Dexec.mainClass=fucking.concurrency.demo.InconsistentReadDemo
🍺 Unsynchronized concurrent counting gives wrong results
Demo class WrongCounterDemo.
Demo description
The main thread starts two task threads that increment a shared
counter concurrently, then checks the final result.
Problem statement
The final count is incorrect.
Quick run
./mvnw compile exec:java -Dexec.mainClass=fucking.concurrency.demo.WrongCounterDemo
🍺 Synchronization on mutable fields
Synchronizing on a volatile field is common, and whoever writes it
naturally assumes this is safe and correct.
# For problem analysis, see the article
Synchronization on mutable fields.
Demo class SynchronizationOnMutableFieldDemo.
Demo description
The main thread starts two task threads that call addListener, then
checks the final result.
Problem statement
The final listener count is incorrect.
Quick run
./mvnw compile exec:java -Dexec.mainClass=fucking.concurrency.demo.SynchronizationOnMutableFieldDemo
🍺 Deadlock caused by symmetric locks
# For problem analysis, see the article
Synchronization on mutable fields
Demo class SymmetricLockDeadlockDemo.
Demo description
The main thread starts two task threads, each of which takes the two
locks in the opposite order from the other.
Problem statement
The task threads deadlock.
Quick run
./mvnw compile exec:java -Dexec.mainClass=fucking.concurrency.demo.SymmetricLockDeadlockDemo
🍺 Livelock caused by reentrant locks
# For a problem description, see the paragraph about livelocks in
the article
Demo class ReentrantLockLivelockDemo.
Demo description
Two task threads try to acquire the lock the other thread holds while
holding their own lock.
Problem statement
The threads release their own lock and immediately re-acquire it,
denying the other thread a chance to acquire both locks. The threads
keep executing: an attempt wastes its work, and overall they make
almost no actual progress. This is a livelock.
Quick run
./mvnw compile exec:java -Dexec.mainClass=fucking.concurrency.demo.ReentrantLockLivelockDemo
🍺 Instruction reordering causes incorrect reads of non-final fields
Demo class FinalInitialDemo.
Demo description
The writer thread calls the class constructor while the reader thread
reads the class’s non-final fields.
Problem statement
When the constructor runs, instruction reordering can move the stores
of non-final fields outside it, so the reader thread may observe the
fields’ default values instead of the values set by the constructor.
(Reordering is not guaranteed; it requires specific hardware and JVM
conditions.)
Quick run
./mvnw compile exec:java -Dexec.mainClass=fucking.concurrency.demo.FinalInitialDemo
🍺 Cyclic thread pool deadlock
Demo class CyclicThreadPoolDeadLockDemo.
Demo description
This example demonstrates the deadlock caused by cyclic dependencies
between tasks when using thread pools, and how to avoid it with
CompletableFuture.
Problem statement
In the badCase, two thread pools, pool1 and pool2, submit tasks
to each other, forming a cyclic dependency.
When the pools’ threads are exhausted, every running task waits for
another task to complete, and the pools deadlock.
The goodCase replaces this with asynchronous chained calls using
CompletableFuture, so no pool thread blocks.
Quick run
./mvnw compile exec:java -Dexec.mainClass=fucking.concurrency.demo.CyclicThreadPoolDeadLockDemo
评论