Skip to main content

Java Threads & Processes

Concurrency in Java begins with understanding the execution units of the Operating System: Threads and Processes.


๐Ÿ‘ถ Beginner Concept: The "Restaurant Kitchen"โ€‹

Multithreading is famously difficult to learn. Imagine a massive, professional kitchen:

  • The Process: This is the entire kitchen building. It has its own isolated walls, 5 ovens, and a giant walk-in fridge (The Heap Memory). If the kitchen building burns down, the restaurant next door is totally fine (Crash Isolation).
  • The Threads: These are the individual Chefs working inside the kitchen. They all share the exact same ovens and fridge (Shared Memory). They each have their own personal cutting board (The Call Stack).
    • The Danger: Because the chefs share the fridge, if Chef A takes the last onion, and Chef B blindly reaches for it at the exact same millisecond, you get a kitchen disaster (A Race Condition).

Process vs Threadโ€‹

AspectProcessThread
DefinitionIndependent unit of execution with its own memory spaceLightweight unit of execution within a process
MemoryIsolated address spaceShares heap with other threads; has own stack
CommunicationIPC (sockets, pipes, shared memory)Shared variables (requires synchronization)
CostExpensive to create/switchCheaper to create/switch
Crash isolationOne process crash doesn't affect othersOne thread crash can bring down the whole process

Creating Threadsโ€‹

// 1. Extending Thread
class MyThread extends Thread {
@Override
public void run() { System.out.println("Running"); }
}
new MyThread().start();

// 2. Implementing Runnable (preferred โ€” allows extending another class)
Runnable task = () -> System.out.println("Running");
new Thread(task).start();

// 3. Using Callable + FutureTask (returns a result)
Callable<Integer> callable = () -> 42;
FutureTask<Integer> future = new FutureTask<>(callable);
new Thread(future).start();
int result = future.get(); // blocks until complete

// 4. Using ExecutorService (production choice)
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> System.out.println("Running"));

๐Ÿ‘ถ Beginner Example: Seeing start() vs run() in Actionโ€‹

This example makes the difference unmistakable:

public class StartVsRun {
public static void main(String[] args) {
Runnable task = () -> System.out.println(
"Executing on: " + Thread.currentThread().getName()
);

Thread t1 = new Thread(task, "Worker-Thread");

// โŒ Wrong โ€” runs synchronously in main thread
t1.run(); // Output: "Executing on: main"

// โœ… Correct โ€” spawns a new OS thread
t1.start(); // Output: "Executing on: Worker-Thread"
}
}
Interview Focus: start() vs run()

Q: Can we directly call the run() method instead of start()? Calling run() directly executes the method synchronously in the current thread, just like any normal method call. It does not spawn a new thread. Calling start() registers the thread with the JVM and OS, transitioning it to the RUNNABLE state, which then invokes run() concurrently.

Thread Lifecycle Statesโ€‹

NEW โ†’ RUNNABLE โ‡„ BLOCKED / WAITING / TIMED_WAITING โ†’ TERMINATED
  • NEW: Thread created but start() not yet called.
  • RUNNABLE: Executing or ready to execute (includes OS "running" and "ready").
  • BLOCKED: Waiting to acquire a monitor lock.
  • WAITING: Waiting indefinitely (Object.wait(), Thread.join(), LockSupport.park()).
  • TIMED_WAITING: Waiting with timeout (Thread.sleep(), Object.wait(timeout)).
  • TERMINATED: Run method completed or exception thrown.
Interview Focus: Thread Control Methods

Q: What is the difference between Thread.sleep() and Object.wait()? 1. Lock Release: sleep() does not release the monitor lock. wait() releases the lock, allowing other threads to enter the synchronized block. 2. Origin: sleep() is a static method in Thread. wait() is an instance method in Object. 3. Usage Context: wait() must be called inside a synchronized block/method. sleep() can be called anywhere.

Thread Coordination: join() and interrupt()โ€‹

Thread.join() โ€” Wait for Another Thread to Finishโ€‹

join() makes the calling thread block and wait until the target thread completes. This is essential when one thread depends on the result of another.

public class JoinExample {
public static void main(String[] args) throws InterruptedException {
Thread dataLoader = new Thread(() -> {
System.out.println("Loading data from database...");
try { Thread.sleep(2000); } catch (InterruptedException e) { /* ... */ }
System.out.println("Data loaded.");
});

dataLoader.start();
dataLoader.join(); // Main thread BLOCKS here until dataLoader finishes
// join(5000) โ€” wait at most 5 seconds, then continue regardless

System.out.println("Processing data..."); // Guaranteed to run AFTER data is loaded
}
}

Thread.interrupt() โ€” Cooperative Cancellationโ€‹

Java threads cannot be forcibly killed (Thread.stop() is deprecated). Instead, interrupt() sets a flag that the target thread must cooperatively check:

Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// do work...
}
System.out.println("Worker noticed interrupt, cleaning up...");
});

worker.start();
Thread.sleep(1000);
worker.interrupt(); // Sets the interrupt flag โ€” does NOT kill the thread

// If the thread is in sleep()/wait()/join(), an InterruptedException is thrown.
// Best practice: catch it and re-set the flag:
// catch (InterruptedException e) { Thread.currentThread().interrupt(); }
Common Mistake: Swallowing InterruptedException

Never write catch (InterruptedException e) { /* ignore */ }. This silently clears the interrupt flag, preventing upstream code from knowing the thread was interrupted. Always re-interrupt: Thread.currentThread().interrupt().

Daemon Threadsโ€‹

Daemon threads are background service threads that the JVM terminates automatically when all non-daemon (user) threads finish. They are used for housekeeping tasks like garbage collection, monitoring, or background cache cleanup.

Thread daemon = new Thread(() -> {
while (true) {
cleanExpiredCacheEntries();
try { Thread.sleep(60_000); } catch (InterruptedException e) { break; }
}
});
daemon.setDaemon(true); // MUST be set BEFORE start()
daemon.start();

// When main() and all user threads finish, this daemon is killed automatically.
// โš ๏ธ Daemon threads do NOT run finally blocks or shutdown hooks on JVM exit!

Use case: Log flushing, heartbeat pings, periodic metric collection โ€” any task where abrupt termination is acceptable.

Deadlockโ€‹

Deadlock occurs when two or more threads are blocked forever, each waiting for a lock held by the other.

Four necessary conditions:

  1. Mutual exclusion โ€” resources cannot be shared.
  2. Hold and wait โ€” holding one lock while waiting for another.
  3. No preemption โ€” locks cannot be forcibly taken.
  4. Circular wait โ€” A waits for B, B waits for A.

๐Ÿ‘ถ Beginner Example: Deadlock in Codeโ€‹

public class DeadlockDemo {
private static final Object LOCK_A = new Object();
private static final Object LOCK_B = new Object();

public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (LOCK_A) { // 1. t1 acquires LOCK_A
System.out.println("T1: Holding LOCK_A, waiting for LOCK_B...");
try { Thread.sleep(50); } catch (InterruptedException e) {}
synchronized (LOCK_B) { // 3. t1 waits for LOCK_B (held by t2) โ†’ DEADLOCK
System.out.println("T1: Got both locks!");
}
}
});

Thread t2 = new Thread(() -> {
synchronized (LOCK_B) { // 2. t2 acquires LOCK_B
System.out.println("T2: Holding LOCK_B, waiting for LOCK_A...");
try { Thread.sleep(50); } catch (InterruptedException e) {}
synchronized (LOCK_A) { // 4. t2 waits for LOCK_A (held by t1) โ†’ DEADLOCK
System.out.println("T2: Got both locks!");
}
}
});

t1.start();
t2.start();
// Both threads hang forever. Use jstack <pid> or ThreadMXBean to detect.
}
}

// FIX: Both threads acquire locks in the SAME order (LOCK_A โ†’ LOCK_B)

Prevention: Always acquire locks in a consistent global order.

Deadlock vs Livelock vs Starvationโ€‹

ProblemBehaviorThreads Blocked?Example
DeadlockThreads wait for each other's locks foreverYes โ€” permanently blockedT1 holds A, waits B; T2 holds B, waits A
LivelockThreads keep reacting to each other but make no progressNo โ€” actively running, but uselessTwo people in a hallway keep stepping aside for each other
StarvationA thread never gets CPU time because higher-priority threads monopolize the schedulerPartially โ€” runnable but never scheduledA low-priority thread never acquires a ReentrantLock(fair=false)
How to detect deadlocks in production
  1. jstack <pid> โ€” prints all thread stack traces; JVM automatically detects and reports deadlock cycles.
  2. ThreadMXBean.findDeadlockedThreads() โ€” programmatic detection you can wire into health checks.
  3. ReentrantLock.tryLock(timeout) โ€” prevents deadlocks by failing instead of blocking forever.