Java Multithreading: Concepts, Implementation, and Management

Modern applications leverage multithreading to execute concurrent tasks efficiently. A thread represents the smallest unit of execution within a process, enabling parallelism even on single-core systems through time-slicing. Understanding how threads are created, managed, and synchronized is critical for building responsive and scalable Java applications.

Thread Lifecycle States

  • New: A Thread object is instantiated but not yet started.
  • Runnable: The thread is eligible to run after calling start(), awaiting CPU allocation.
  • Running: The thread is actively executing its run() method.
  • Blocked: The thread is temporari paused due to:
    • Waiting: Invoking wait() on an object monitor.
    • Synchronized: Attempting to acquire a locked monitor via synchronized or Lock.
    • Sleeping: Calling Thread.sleep() — does not release locks.
    • Yielding: Voluntarily relinquishing CPU via yield().
  • Terminated: The thread completes execution of run() or is forcibly stopped (deprecated methods like stop() should be avoided).

Blocked threads transition back to Runnable when conditions are met: sleep duration expires, I/O completes, or notify()/notifyAll() is invoked on the monitor object.

Thread Creation Approaches

1. Extending Thread Class

Direct subclassing of java.lang.Thread overrides the run() method.

class Worker extends Thread {
    @Override
    public void run() {
        System.out.println("Executing via Thread extension");
    }
}

// Usage
Worker worker = new Worker();
worker.start();

2. Implementing Runnable

More flexible than inheritance; separates task logic from thread lifecycle.

class Task implements Runnable {
    @Override
    public void run() {
        System.out.println("Executing via Runnable");
    }
}

// Usage
Task task = new Task();
Thread thread = new Thread(task);
thread.start();

3. Implementing Callable

Similar to Runnable but supports return values and checked exceptions via call().

import java.util.concurrent.*;

class ComputableTask implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Result from Callable task";
    }
}

// Usage with ExecutorService
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<String> future = pool.submit(new ComputableTask());
String result = future.get(); // Blocks until completion
pool.shutdown();

4. Using Thread Pools

Recommended for production systems to avoid excessive thread creation.

ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> System.out.println("Task executed via pool"));
executor.shutdown();

Synchronization and Thread Safety

Using ReentrantLock

Explicit locking provides finer control than synchronized blocks.

import java.util.concurrent.locks.ReentrantLock;

class SafeCounter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

Using synchronized

Implicit locking on object monitors. Static methods lock on class; instance methods lock on this.

class SyncCounter {
    private static int counter = 0;

    public static synchronized void increment() {
        counter++;
    }
}

Inter-Thread Communication

Threads coordinate using shared state and signaling mechanisms:

  • Wait/Notify: Threads wait on object monitors and are signaled via notify() or notifyAll().
  • Condition Variables: Used with Lock objects for more precise control than wait/notify.
  • Shared Variables: Access to volatile or atomic variables ensures visibility across threads.

Deadlock Detection

Using jstack

Run jps to identfiy Java process IDs, then use jstack <pid> to dump thread states. Deadlocks are explicitly flagged with stack traces showing circular waits.

Using jconsole

The GUI tool jconsole (found in JDK’s bin directory) provides real-time monitoring of thread states and detects deadlocks visually under the "Threads" tab.

Thread Pool Types

  • newCachedThreadPool(): Creates threads as needed; reuses idle ones.
  • newFixedThreadPool(n): Maintains exactly n active threads.
  • newSingleThreadExecutor(): Serial execution with one thread.
  • newScheduledThreadPool(n): Supports delayed and periodic tasks.
  • newWorkStealingPool(): Uses fork-join framework for work stealing (default parallelism = CPU cores).

For full control, instantiate ThreadPoolExecutor directly:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2,           // corePoolSize
    8,           // maximumPoolSize
    60L,         // keepAliveTime
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

Why Use Thread Pools?

Thread creation and destruction are expensive. Pools reduce overhead by reusing threads, limit resource consumption, and provide structured task queuing and rejection policies. They also simplify lifecycle management and improve application stability under load.

Deadlock Example

A classic deadlock occurs when two threads hold resources the other needs.

public class DeadlockExample {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lockA) {
                System.out.println("Thread 1: Locked A");
                try { Thread.sleep(1000); } catch (InterruptedException e) {}
                synchronized (lockB) {
                    System.out.println("Thread 1: Locked B");
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (lockB) {
                System.out.println("Thread 2: Locked B");
                try { Thread.sleep(1000); } catch (InterruptedException e) {}
                synchronized (lockA) {
                    System.out.println("Thread 2: Locked A");
                }
            }
        }).start();
    }
}

Tags: java multithreading thread executorservice ReentrantLock

Posted on Wed, 30 Sep 2026 16:23:13 +0000 by Pawn