Java Multithreading
Quick Reference Cheatsheet
Thread Creation
// 1. Extend Thread
class MyThread extends Thread {
public void run() { /* work */ }
}
new MyThread().start();
 
// 2. Implement Runnable (preferred)
class MyRun implements Runnable {
public void run() { /* work */ }
}
new Thread(new MyRun()).start();
 
// 3. Lambda shorthand
Thread t = new Thread(() -> { /* work */ }, "name");
t.start();
⚠ Calling run() directly does NOT start a new thread. Always use start().
Thread Types
Main ThreadAuto-created, runs program entry point
User ThreadJVM waits for all to finish before exit
Daemon ThreadBackground; JVM exits when all user threads done
t.setDaemon(true);   // must call before start()
GC thread is a daemon thread. Thread.currentThread().getState() returns RUNNABLE, not RUNNING — Java has no RUNNING state.
Thread Lifecycle
NEW
RUNNABLE
RUNNING (theoretical)
BLOCKED
/
WAITING
/
TIMED_WAITING
TERMINATED
NEW — object created, start() not calledRUNNABLE — ready, waiting for CPUBLOCKED — waiting for monitor lockWAITING — wait() / join() with no timeoutTIMED_WAITING — sleep(ms) / join(ms)TERMINATED — run() completed
Thread Methods
MethodDescription
t.start()NEW → RUNNABLE, creates thread
t.join()Caller waits for t to finish
Thread.sleep(ms)Current thread sleeps, keeps lock
t.interrupt()Interrupt sleep or execution
t.yield()Hint: give up CPU slice
t.getName()Get thread name
t.getState()Returns Thread.State enum
t.getPriority()1–10, default 5 (NORM_PRIORITY)
t.setPriority(n)Hint only; no guarantee
t.setDaemon(true)Before start() only
Thread.currentThread()Reference to running thread
Calling start() twice → IllegalThreadStateException. Uncaught exceptions don't propagate to parent thread.
Synchronization
// Synchronized method
synchronized void increment() { count++; }
 
// Synchronized block (finer control)
synchronized(this) { count++; }
Indefinite blockingNo fairnessLocks reads tooNot interruptible
Race condition — multiple threads on shared critical section
Mutual exclusion — only one thread accesses at a time
Intrinsic lock — acquired automatically with synchronized
Monitor — object-level lock used by wait/notify
Locks (java.util.concurrent.locks)
ReentrantLock lock = new ReentrantLock(true); // fair
 
// Typical usage pattern
lock.lock();
try { /* critical section */ }
finally { lock.unlock(); }
 
// tryLock — non-blocking attempt
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try { /* work */ }
finally { lock.unlock(); }
}
 
// lockInterruptibly — wait but allow interrupt
lock.lockInterruptibly();
 
// ReadWriteLock
ReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.readLock().lock(); // multiple readers OK
rwl.writeLock().lock(); // exclusive
⚠ Reentrant = same thread can re-acquire. Tracks lock count; needs equal unlock() calls. More unlock() than lock() → exception.
Thread Communication
MethodEffect
wait()Release lock, pause until notified
wait(ms)Release lock, pause up to ms
notify()Wake one waiting thread
notifyAll()Wake all waiting threads
// Consumer
while (resource == null) wait();
// use resource
notify();
 
// Producer
while (resource != null) wait();
// produce resource
notify();
sleep() keeps lock  |  wait() releases lock
Deadlock
Mutual ExclusionHold & WaitNo PreemptionCircular Wait
// Deadlock: A.use(B) vs B.use(A)
// Fix: consistent lock ordering
synchronized(B) {
synchronized(A) { /* always B then A */ }
}
 
// Or use tryLock to avoid blocking
if (lockA.tryLock() && lockB.tryLock()) { ... }
ExecutorService
ExecutorService es =
Executors.newFixedThreadPool(3);
Executors.newCachedThreadPool();
Executors.newSingleThreadExecutor();
 
es.submit(() -> { /* task */ });
 
Future<String> f = es.submit(() -> "result");
f.get(); // blocks, throws checked exs
f.get(2, TimeUnit.SECONDS); // timeout
f.cancel(true); // interrupt if running
 
es.invokeAll(list); // List<Future>
es.invokeAny(list); // first result, stops rest
 
es.shutdown();
es.awaitTermination(10, TimeUnit.SECONDS);
Fixed pool: extra tasks queue in unbounded LinkedQueue. invokeAll can take time limit; cancelled tasks throw CancellationException on .get().
Runnable vs Callable
FeatureRunnableCallable<T>
Packagejava.langjava.util.concurrent
Methodrun()call()
ReturnvoidT
Exceptions❌ unchecked only✅ checked
new Thread(x)
ExecutorService
ScheduledExecutorService
ScheduledExecutorService ses =
Executors.newScheduledThreadPool(1);
 
// Run once after delay
ses.schedule(task, 5, TimeUnit.SECONDS);
 
// Repeat with fixed period (next start)
ses.scheduleAtFixedRate(task, init, period, unit);
 
// Repeat with fixed delay (after finish)
ses.scheduleWithFixedDelay(task, init, delay, unit);
 
// Self-shutdown after 10s
ses.schedule(() -> ses.shutdown(), 10, TimeUnit.SECONDS);
AtFixedRate: period from start of last run  | WithFixedDelay: delay after completion
CountDownLatch vs CyclicBarrier
// CountDownLatch — one-time gate
CountDownLatch latch = new CountDownLatch(3);
// in each task's finally block:
latch.countDown();
// in main:
latch.await();
latch.await(5, TimeUnit.SECONDS);
 
// CyclicBarrier — reusable meeting point
CyclicBarrier b = new CyclicBarrier(3, () ->
System.out.println("all arrived"));
// each thread calls:
b.await(); // waits for all parties
b.reset(); // reuse
Latch: not reusableBarrier: reusable / reset()
CompletableFuture
CompletableFuture<String> cf =
CompletableFuture.supplyAsync(() -> "hey");
 
cf.get(); // blocks, checked exs
cf.join(); // blocks, unchecked exs
cf.getNow("fallback"); // instant, no block
 
cf.thenApply(s -> s + "!")
.thenAccept(System.out::println)
.exceptionally(e -> null);
 
CompletableFuture.allOf(cf1, cf2).join();
Runs as daemon thread by default — use .get()/.join() or main will exit.
Future vs CompletableFuture
FeatureFutureCompletableFuture
Non-blocking✅ thenAccept etc
Chaining✅ thenCompose/Combine
Manual complete✅ .complete(val)
Error handlingtry-catch✅ .exceptionally()
allOf / anyOf
ForkJoinPool
// RecursiveTask — returns value
class MyTask extends RecursiveTask<Integer> {
protected Integer compute() {
if (small) return base;
MyTask sub = new MyTask(...);
sub.fork(); // async submit
return sub.join() + compute();
}
}
// RecursiveAction — no return value
// Engine behind parallelStream()
Each thread has own deque (LIFO pop own, FIFO steal from others). Work-stealing keeps all cores busy.
Atomic Variables
// java.util.concurrent.atomic
AtomicInteger ai = new AtomicInteger(0);
ai.incrementAndGet();
ai.decrementAndGet();
ai.addAndGet(5);
ai.compareAndSet(expected, update);
ai.get();
AtomicIntegerAtomicLongAtomicBooleanAtomicReference<T>
Lock-free; uses CPU CAS instructions. No synchronized needed.
Isolation Levels (DB / Spring @Transactional)
LevelDirty ReadNon-Rep ReadPhantom Read
READ_UNCOMMITTED✅ possible✅ possible✅ possible
READ_COMMITTED❌ prevented✅ possible✅ possible
REPEATABLE_READ✅ possible
SERIALIZABLE
Dirty ReadRead uncommitted data (could roll back)
Non-RepeatableSame row, diff values in same tx
Phantom ReadSame query, diff row count in same tx
Lost UpdateTwo tx overwrite each other
@Transactional(isolation = Isolation.READ_COMMITTED)
Lost updates NOT prevented by isolation alone → use @Version (optimistic) or pessimistic locking
False Sharing
CPU reads memory in 64-byte cache lines. If two variables share one line, updating either on different cores invalidates the other core's cache — causing constant re-fetch.
long a;
long p1,p2,p3,p4,p5,p6;
long b;
@Contended
volatile long a;
needs JVM flag
BlockingQueue
BlockingQueue<Integer> bq =
new LinkedBlockingQueue<>(3); // bounded
new ArrayBlockingQueue<>(3);
 
bq.put(x); // blocks if full
bq.take(); // blocks if empty
bq.offer(x); // non-blocking, returns bool
bq.poll(); // non-blocking, returns null
bq.peek(); // see head, no remove
Thread-safe. No null elements. Ideal for producer-consumer without manual wait/notify.
JAVA MULTITHREADING CHEATSHEET  ·  java.util.concurrent  ·  Thread  ·  Locks  ·  Executors