Multithreading is a fundamental skill for any serious Java developer. Whether you're optimizing performance, improving responsiveness, or handling thousands of concurrent users—understanding multithreading is essential.
This cheat sheet provides a quick-reference guide packed with syntax, concepts, tools, patterns, and expert tips to write robust concurrent code in Java.
🧵 Core Concepts of Java Multithreading
Concept | Summary |
---|---|
Thread | Lightweight unit of execution |
Runnable/Callable | Task abstraction for threading |
Concurrency | Multiple tasks in overlapping time |
Parallelism | Multiple tasks running simultaneously |
Shared State | Multiple threads accessing same memory |
Race Condition | Incorrect behavior from unsynchronized access |
Deadlock | Threads waiting on each other forever |
🔄 Thread Lifecycle States
NEW → RUNNABLE → BLOCKED → WAITING → TERMINATED
NEW
: Created but not startedRUNNABLE
: Ready to run or runningBLOCKED
: Waiting for lockWAITING
: Waits indefinitelyTIMED_WAITING
: Waits with timeoutTERMINATED
: Done
📦 Thread Creation Syntax
Traditional
Thread t = new Thread(() -> doWork());
t.start();
ExecutorService
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> doWork());
pool.shutdown();
Virtual Threads (Java 21+)
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
exec.submit(() -> doWork());
}
🧠 Java Memory Model Tips
- Use
volatile
for visibility across threads synchronized
ensures mutual exclusion and visibility- Use atomic classes (
AtomicInteger
,AtomicReference
) for lock-free updates
🔒 Locking & Coordination Tools
Tool | Use |
---|---|
synchronized |
Basic lock and memory visibility |
ReentrantLock |
Lock with more control (timeouts, fairness) |
ReadWriteLock |
Separate read/write locking |
StampedLock |
Optimized read/write locking |
wait()/notify() |
Coordination using intrinsic lock |
join() |
Wait for thread to finish |
sleep() |
Pause thread |
CountDownLatch |
Wait until count reaches zero |
Semaphore |
Control concurrent access |
CyclicBarrier |
Wait until all parties reach a point |
🚀 High-Level Concurrency Utilities
Class | Use |
---|---|
ExecutorService |
Thread pooling |
ForkJoinPool |
Recursive parallelism |
CompletableFuture |
Async result handling |
BlockingQueue |
Producer-consumer |
ConcurrentHashMap |
Thread-safe map |
CopyOnWriteArrayList |
Thread-safe list |
Phaser |
Dynamic synchronization barrier |
🔁 Common Use Cases
✅ Producer-Consumer
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
✅ Parallel File Processing
Use ExecutorService
or ForkJoinPool
to split and process.
✅ Asynchronous Tasks
CompletableFuture.supplyAsync(() -> compute())
.thenApply(result -> transform(result));
🛠️ Debugging & Monitoring Tools
Tool | Purpose |
---|---|
jstack |
Thread dumps |
JVisualVM |
Thread inspector |
Java Mission Control (JMC) |
Lock contention & thread monitoring |
IntelliJ Debugger | Multithreaded debugging |
JCStress |
Memory model stress tests |
JFR |
Java Flight Recorder for tracing |
📘 Design Patterns for Concurrency
Pattern | Usage |
---|---|
Worker Thread | Reuse fixed number of threads |
Future Task | Execute with result |
Thread-per-message | New thread per request |
Producer-Consumer | Shared queue between producers/consumers |
Reactor | Event-driven I/O |
Balking | Skip repeated work under condition |
Thread Pool | Bounded threads to prevent resource exhaustion |
🚫 Anti-Patterns to Avoid
- Creating unbounded threads (
new Thread()
in loops) - Logging inside
synchronized
blocks Thread.sleep()
for timing coordination- Ignoring executor shutdown
- Accessing shared mutable state unsafely
✅ Golden Rules for Safe Multithreading
- Prefer
ExecutorService
over raw threads - Always shut down thread pools
- Avoid shared mutable state; if needed, use locks or atomic variables
- Use
volatile
carefully—only for visibility, not atomicity - Use structured logging and thread naming for observability
📌 What's New in Java Versions?
Java 8
- Lambdas with
Runnable
,Callable
CompletableFuture
parallelStream()
Java 9
Flow API
(Reactive Streams)
Java 11
CompletableFuture.delayedExecutor()
Java 21
- ✅ Virtual Threads via
Executors.newVirtualThreadPerTaskExecutor()
- ✅ Structured Concurrency
- ✅ Scoped Values
🧠 Expert-Level FAQ
Q1: What’s the difference between a thread and a task?
A task is a unit of work; a thread is the vehicle to execute it.
Q2: How is volatile
different from synchronized
?
volatile
ensures visibility; synchronized
ensures both visibility and mutual exclusion.
Q3: Can I mix ForkJoin and Executors?
Yes, but be cautious about thread exhaustion and resource contention.
Q4: When should I use virtual threads?
For lightweight, high-concurrency, I/O-heavy tasks (e.g., HTTP calls).
Q5: What causes deadlocks?
Circular wait between threads holding different locks.
Q6: Are thread priorities reliable?
No. They are advisory and not enforced across all OSes.
Q7: What is false sharing?
Performance issue when multiple threads update variables on the same cache line.
Q8: How do I handle uncaught thread exceptions?
Thread.setDefaultUncaughtExceptionHandler((t, e) -> log(e));
Q9: Why use CompletableFuture
over Future
?
Supports chaining, exception handling, and async composition.
Q10: Is ThreadLocal
safe?
Yes, for isolating data per thread. Avoid memory leaks by cleaning up.
🎯 Conclusion and Key Takeaways
This cheat sheet is your go-to reference for writing concurrent Java code:
- Master the syntax and semantics of threads, executors, and locks
- Know your tools—debuggers, profilers, analyzers
- Embrace modern constructs like
CompletableFuture
,ForkJoinPool
, and virtual threads - Avoid common concurrency bugs with best practices and patterns