Multithreading Cheat Sheet for Java Developers

Illustration for Multithreading Cheat Sheet for Java Developers
By Last updated:

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 started
  • RUNNABLE: Ready to run or running
  • BLOCKED: Waiting for lock
  • WAITING: Waits indefinitely
  • TIMED_WAITING: Waits with timeout
  • TERMINATED: 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