Java Interview Treads, Exceptions - Yash-777/MyWorld GitHub Wiki
Thread: Group of statements that are executed separately. Thread is a "light weight processor" which share common features of a processor.
ThreadCls t = new ThreadCls();
t.run(); // normal function call
t.start(); // Starts new Thread
t.start(); // java.lang.IllegalThreadStateException- Why use Runnable when Thread is already available?
- Java does not support multiple inheritance (a class cannot extend more than one class).
- If we extend the Thread class, we cannot extend any other class.
- But if we implement the Runnable interface, we are still free to extend another class.
Class MyClass extends Thread {} // Now this class cannot extend any other class.
Class MyClass extends Thread, SuperClass {} // {WRONG} β multiple inheritance not possible
Class MyClass extends SuperClass implements Runnable {} // MyTask can still extend another class if needed.Thread Example with DeadLock and Precautions to avoid it
| Source | Deadlock | Avoid Deadlock |
|---|---|---|
NEW β RUNNABLE β RUNNING β TERMINATED
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
public class Thread implements Runnable {
private volatile int threadStatus; // default indicates thread 'not yet started'
public static native void sleep(long millis) throws InterruptedException;
private native void start0();
/* Causes this thread to begin execution; the Java Virtual Machinecalls the run method of this thread.
* It is never legal to start a thread more than once.In particular,
* a thread may not be restarted once it has completedexecution. */
public synchronized void start() {
// A zero status value corresponds to state "NEW".
if (threadStatus != 0)
throw new IllegalThreadStateException();
// Notify the group that this thread is about to be started
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
}
|
public class DeadlockExample {
private static final Object firstLock = new Object();
private static final Object secondLock = new Object();
// Method 1: Extending Thread class
private static class MyThread extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName() + " started...");
synchronized (firstLock) {
System.out.println(Thread.currentThread().getName() + " Holding lock 1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " Waiting for lock 2...");
synchronized (secondLock) {
System.out.println(Thread.currentThread().getName() + " Holding lock 1 & 2...");
}
}
System.out.println(Thread.currentThread().getName() + " ended.");
}
}
// Method 2: Implementing Runnable interface (preferred)
private static class MyRunnable implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + " started...");
synchronized (secondLock) {
System.out.println(Thread.currentThread().getName() + " Holding lock 2...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " Waiting for lock 1...");
synchronized (firstLock) {
System.out.println(Thread.currentThread().getName() + " Holding lock 1 & 2...");
}
}
System.out.println(Thread.currentThread().getName() + " ended.");
}
}
}
Example: public static void main(String args[])
// Using Thread class
MyThread t1 = new MyThread(); // Lock1, Lock2
t1.start();
MyRunnable t2 = new MyRunnable(); // Lock2, Lock1
new Thread(t2).start();
//
// Thread lifecycle : NEW β RUNNABLE β RUNNING β TERMINATED
MyThread t11 = new MyThread();
t11.start(); // β
First time β works fine : on start() it moves from NEW to RUNNABLE/RUNNING state.
// π« A terminated thread cannot go back to NEW state.
try { Thread.sleep(10000); } catch (InterruptedException e) {}
t11.start(); // β Second time β throws exception β IllegalThreadStateException.
//
// Avoid Deadlock: Lock Ordering (Most Common)
// Always acquire locks in the same order across all threads: Locks synchronized (firstLock) then synchronized (secondLock)
// Different Objects Thread start no problem
new MyThread().start(); // new object β
First time β works fine
new MyThread().start(); // new object β
First time β works fine DeadLock
// Runnable : no dead-lock. Both start at same time but one thread acquires the lock others wait (Lock2)
MyRunnable t22 = new MyRunnable();
new Thread(t22).start();
new Thread(t22).start();
|
Maintain Same Lock Ordering: Always acquire locks in a consistent global order
Both threads follow the same order β no circular waiting β no deadlock. (firstLock β secondLock)
Runnable task = () -> {
synchronized (firstLock) {
System.out.println(Thread.currentThread().getName() + " holding firstLock");
//
try { Thread.sleep(100); } catch (InterruptedException e) {}
//
synchronized (secondLock) {
System.out.println(Thread.currentThread().getName() + " holding lock1 & secondLock");
}
}
};
Thread thread1 = new Thread(() -> {
// Lock firstLock, do work, release
synchronized (firstLock) {
System.out.println("Thread 1: Working with Resource 1");
}
// Then lock secondLock
synchronized (secondLock) {
System.out.println("Thread 1: Working with Resource 2");
}
});
// Method 2: Implementing Runnable interface (preferred)
private static class MyRunnable implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + " started...");
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " Waiting for lock 1...");
synchronized (firstLock) {
System.out.println(Thread.currentThread().getName() + " Holding lock 1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
System.out.println(Thread.currentThread().getName() + " Waiting for lock 2...");
synchronized (secondLock) {
System.out.println(Thread.currentThread().getName() + " Holding 2...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
System.out.println(Thread.currentThread().getName() + " ended.");
}
}private static class MyRunnable implements Runnable {
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " started...");
for (int i = 0; i < 3; i++) {
try {
// Try to acquire first lock
if (firstLock.tryLock(50, TimeUnit.MILLISECONDS)) {
try {
System.out.println(threadName + ": Acquired first lock");
// Try to acquire second lock
if (secondLock.tryLock(50, TimeUnit.MILLISECONDS)) {
try {
System.out.println(threadName + ": Acquired both locks!");
} finally {
secondLock.unlock();
}
}
} finally {
firstLock.unlock();
}
}
// If couldn't get both locks, retry
System.out.println(threadName + ": Couldn't acquire both locks, retrying...");
} catch (InterruptedException e) {
System.out.println("InterruptedException:"+ e.getMessage());
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + " ended.");
}
}Use Higher-Level Concurrency Utilities: Consider ExecutorService, ConcurrentHashMap, etc. Deadlock Detection: Use tools like JConsole or VisualVM to detect deadlocks in running applications |
| Thread States | State Transition Diagram |
|---|---|
NEW β RUNNABLE β RUNNING β TERMINATED
β β
BLOCKED WAITING/TIMED_WAITINGThread t = new Thread(); // NEW statet.start(); // Starts the thread (NEW β RUNNABLE)4. BLOCKED : Thread is waiting for a monitor lock. static final Object lock = new Object();
synchronized(lock) { // Another thread holds lock
// Waiting to enter - BLOCKED state
}lock.wait(); // WAITING - Object wait
Thread.join(); // WAITING - Wait for thread to die (RUNNING β WAITING)
LockSupport.park(); // WAITINGThread.sleep(1000); // TIMED_WAITING - Pauses thread for time(RUNNING β TIMED_WAITING)
lock.wait(1000); // TIMED_WAITING
Thread.join(1000); // TIMED_WAITING - Wait for thread with timeout(RUNNING β TIMED_WAITING)
lock.notify(); // β
Wake up one of the waiting threadNEW β RUNNABLE β (enters synchronized) β RUNNABLE β wait() β WAITING
β β
(notify called) (notify + lock available)
β β
BLOCKED (waiting to re-acquire lock) β (lock acquired) β RUNNABLE β TERMINATEDβ Problem : obj.notify() without synchronized block
// This throws IllegalMonitorStateException
obj.notify(); // β WRONG - not in synchronized blocknotify() in synchronized block
synchronized(obj) {
obj.notify(); // β
CORRECT - inside synchronized block
}t.interrupt() instead of notify()
t.interrupt(); // This interrupts the thread, doesn't notify it properly
interrupt() vs notify() difference:
|
Golden Rule: Always acquire the monitor lock (using synchronized) before calling wait(), notify(), or notifyAll()!
child Thread = synchronized(obj) { lock.wait(); }
main Thread = synchronized(obj) { lock.notify(); }
public class ThreadLifecycleDemoComplete {
static final Object lock = new Object();
static volatile boolean dataReady = false;
public static void main(String[] args) throws InterruptedException {
// CHILD THREAD (Consumer)
Thread childThread = new Thread(() -> {
System.out.println("Child: Started | State: " + Thread.currentThread().getState());
try {
synchronized(lock) {
System.out.println("Child: Acquired lock");
while (!dataReady) {
System.out.println("Child: Data not ready, calling wait()");
System.out.println("Child: State before wait(): " + Thread.currentThread().getState());
lock.wait(); // WAITING state (releases lock)
System.out.println("Child: Woke up from wait()!");
}
System.out.println("Child: Data is ready! Processing...");
System.out.println("Child: State after wake up: " + Thread.currentThread().getState());
}
} catch (InterruptedException e) {
System.err.println("Child: Interrupted - " + e.getMessage());
}
System.out.println("Child: Finished");
}, "Child-Thread");
// MAIN THREAD (Producer)
System.out.println("Main: Child thread state: " + childThread.getState()); // NEW
childThread.start();
System.out.println("Main: Child thread state after start(): " + childThread.getState()); // RUNNABLE
Thread.sleep(1000); // Let child thread enter wait()
System.out.println("Main: Child thread state (in wait): " + childThread.getState()); // WAITING
// Prepare data
Thread.sleep(2000);
System.out.println("\nMain: Preparing data...");
dataReady = true;
// Notify the waiting thread
synchronized(lock) {
System.out.println("Main: Acquired lock");
System.out.println("Main: Calling notify()");
lock.notify(); // β
Wake up child thread
System.out.println("Main: notify() called");
System.out.println("Main: Child thread state (BLOCKED, waiting for lock): " + childThread.getState());
Thread.sleep(1000); // Hold lock
System.out.println("Main: Releasing lock...");
} // Lock released
System.out.println("Main: Lock released");
Thread.sleep(500);
System.out.println("Main: Child thread state (should be RUNNABLE): " + childThread.getState());
childThread.join();
System.out.println("Main: Child thread state (final): " + childThread.getState()); // TERMINATED
}
}
|
Thread: A thread is a lightweight subprocess that runs independently within a program.
ExecutorService is a high-level thread management framework that manages a pool of worker threads for you. Instead of manually creating and managing threads, you submit tasks and ExecutorService handles the execution.
Simple Analogy Think of it like a restaurant kitchen manager:
- Without ExecutorService: You hire a new chef for every order (create new thread each time) β
- With ExecutorService: You have a team of chefs ready to handle orders as they come (thread pool) β
Why Use ExecutorService?
| β Problem with Manual Thread Creation | β Solution with ExecutorService |
|---|---|
// Creating threads manually - BAD for many tasks!
for (int i = 0; i < 1000; i++) {
Thread t = new Thread(() -> {
// Do some work
});
t.start(); // Creates 1000 threads! π± System overload!
}
|
// Thread pool - GOOD!
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit( () -> { // Callable call()
// Do some work
}); // Only 10 threads handle all 1000 tasks! π
}
executor.shutdown();
|
Types of ExecutorService (Thread Pools)
- Fixed Thread Pool: Fixed number of threads, reuses them.
ExecutorService executor = Executors.newFixedThreadPool(5);
// Creates exactly 5 threads - good for CPU-bound tasks- Cached Thread Pool: Creates new threads as needed, reuses idle ones.
ExecutorService executor = Executors.newCachedThreadPool();
// Dynamic pool - good for many short-lived tasks- Single Thread Executor: Only one thread executes tasks sequentially.
ExecutorService executor = Executors.newSingleThreadExecutor();
// Tasks execute one at a time in order - good for sequential processing- Scheduled Thread Pool: Schedule tasks to run after delay or periodically.
// Good for scheduled/periodic tasks
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
// 1. Schedule one-time task with delay
System.out.println("Scheduling task with 3 second delay...");
scheduler.schedule(() -> {
System.out.println("Task executed after 3 seconds!");
}, 3, TimeUnit.SECONDS);Complete Example with Future
javaimport java.util.concurrent.*;
import java.util.ArrayList;
import java.util.List;
public class ExecutorServiceWithFuture {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(3);
// Submit tasks that return results
List<Future<Integer>> futures = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
int num = i;
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("Calculating square of " + num +
" by " + Thread.currentThread().getName());
Thread.sleep(1000);
return num * num;
}
});
futures.add(future);
}
// Get results
System.out.println("\nGetting results:");
for (int i = 0; i < futures.size(); i++) {
Future<Integer> future = futures.get(i);
Integer result = future.get(); // Blocking call
System.out.println("Result " + (i + 1) + ": " + result);
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("All tasks completed!");
}
}// @since 1.8 public class CompletableFuture implements Future, CompletionStage { }