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
    Thread lifecycle : NEW β†’ RUNNABLE β†’ RUNNING β†’ TERMINATED
  • start(); βœ… On start() it moves from NEW to RUNNABLE/RUNNING state.
  • A thread can be started only once. If we call start() again on the same thread object, Java throws IllegalThreadStateException because a terminated thread cannot be restarted.
  • 🚫 A terminated thread cannot go back to NEW state.
@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.");
    }
  } 
}
      
If Both threads are waiting forever β†’ Deadlock.
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");
    }
  }
};
  
Avoid Nested Synchronization/Locks: Minimize holding multiple locks simultaneously
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


Java Thread Lifecycle - States and Methods

Thread States State Transition Diagram
NEW β†’ RUNNABLE β†’ RUNNING β†’ TERMINATED
        ↕           ↕
    BLOCKED    WAITING/TIMED_WAITING
1. NEW : Thread is created but not started yet.
Thread t = new Thread();  // NEW state
2. RUNNABLE : Thread is ready to run, waiting for CPU time.
t.start();  // Starts the thread (NEW β†’ RUNNABLE)
3. RUNNING : Thread is executing (has CPU).
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
}
5. WAITING : Thread is waiting indefinitely for another thread's action.
lock.wait();        // WAITING - Object wait
Thread.join();      // WAITING - Wait for thread to die (RUNNING β†’ WAITING)
LockSupport.park(); // WAITING
6. TIMED_WAITING : Thread is waiting for a specific time period.
Thread.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 thread
7. TERMINATED (DEAD) : Thread has completed execution. State Transitions of the Example: Child Thread States
NEW β†’ 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 block
Wrap notify() in synchronized block
synchronized(obj) {
    obj.notify();  // βœ… CORRECT - inside synchronized block
}
Using t.interrupt() instead of notify()
t.interrupt();  // This interrupts the thread, doesn't notify it properly
    interrupt() vs notify() difference:
  • interrupt(): Forcefully interrupts the thread β†’ throws InterruptedException
  • notify(): Properly wakes up a waiting thread β†’ continues normal execution
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
  }
}
      

ExecutorService

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)

  1. Fixed Thread Pool: Fixed number of threads, reuses them.
ExecutorService executor = Executors.newFixedThreadPool(5);
// Creates exactly 5 threads - good for CPU-bound tasks
  1. Cached Thread Pool: Creates new threads as needed, reuses idle ones.
ExecutorService executor = Executors.newCachedThreadPool();
// Dynamic pool - good for many short-lived tasks
  1. 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
  1. 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!");
    }
}

CompletableFuture

// @since 1.8 public class CompletableFuture implements Future, CompletionStage { }

⚠️ **GitHub.com Fallback** ⚠️