Comprehensive Overview of Java Threading Concepts

Java Threading

Prior to JDK 1.2, Java threading was implemented using green threads, which are user-level threads that operate independently of the operating system. In this model, the JVM simulated multithreading behavior rather than relying on native OS capabilities. Green threads had limitations such as inability to utilize asynchronous I/O or leverage multiple CPU cores effectively.

Starting from JDK 1.2, Java adopted native threads—the JVM now directly uses OS kernel threads for implementation, allowing the OS to manage thread scheduling and execution.

Relationship Between Threads and Processes

Memory Areas in JVM (Post-JDK 1.8)

Each process can contain multiple threads, all sharing heap and method area resources (metaspace in JDK 1.8+). However, each thread has its own program counter register, JVM stack, and native method stack.

Shared Resources: Heap and Method Area

The heap stores all newly created objects, while the method area holds loaded class metadata, constants, static variables, and compiled bytecode.

Private Resources: Program Counter, JVM Stack, and Native Method Stack

Program Counter Register

The program counter keeps track of the current instruction being executed. When switching between threads, it allows resumption at the correct execution point.

  1. Bytecode interpreter updates the program counter to control flow.
  2. In multi-threading scenarios, it records the last executed position per thread.
  3. For native methods, the value is undefined; otherwise, it points to the next bytecode instruction.

JVM Stack and Native Method Stack

The JVM stack handles Java method execution by creating frames containing local variables, operand stacks, and runtime constant pool references. Each method call corresponds to a frame entering and exiting the stack.

The native method stack supports native method calls. In HotSpot VM, both stacks are merged into one, ensuring thread isolation through private memory allocation.

Thread States in Java

Every thread exists in exactly one of six possible states:

  • NEW: Thread created but not yet started via start()
  • RUNNABLE: Thread ready to execute, waiting for CPU time
  • BLOCKED: Waiting to acquire a monitor lock
  • WAITING: Waiting indefinitely for another thread's action
  • TIME_WAITING: Waiting for a specified duration before returning
  • TERMINATED: Thread completed execution

Common Thread Creation Methods

There are three standard approaches to create threads:

Implementing Runnable Interface

This approach defines a task that can be run in a thread but does not constitute a thread itself.

public class TaskExample implements Runnable {
    @Override
    public void run() {
        System.out.println("Running task in thread");
    }
}

public class Main {
    public static void main(String[] args) {
        TaskExample task = new TaskExample();
        Thread worker = new Thread(task);
        worker.start();
    }
}

Using Callable Interface

Unlike Runnable, Callable supports return values wraped in FutureTask.

import java.util.concurrent.*;

public class TaskWithResult implements Callable<Integer> {
    public Integer call() {
        return 42;
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        TaskWithResult task = new TaskWithResult();
        FutureTask<Integer> future = new FutureTask<>(task);
        Thread executor = new Thread(future);
        executor.start();
        System.out.println(future.get());
    }
}

Extending Thread Class

Direct subclassing of Thread is another way to define custom thread behavior.

public class CustomThread extends Thread {
    @Override
    public void run() {
        System.out.println("Custom thread running");
    }
}

public class Main {
    public static void main(String[] args) {
        CustomThread thread = new CustomThread();
        thread.start();
    }
}

Calling run() directly instead of start() executes the method within the calling thread context, not as a separate thread.

Thread Interruption

Thread interruption is a flag indicating that a thread should stop or handle cancellation. While normal execution remains unaffected, certain blocking operations react to interruptions.

Impact on Blocking Operations

When a thread is sleeping or waiting, calling interrupt() causes an InterruptedException. Before throwing the exception, the JVM clears the interrupt status. Subsequently, isInterrupted() returns false.

Wait/Notify Mechanism

If a thread waits on an object and receives an interrupt signal, it throws InterruptedException immediately. This differs from normal wake-up because interrupted threads exit the wait block in to catch clauses rather than continuing execution after wait().

Sleep and Join

To awaken a sleeping or joining thread early, use interrupt(). This transfers control to the exception handler where further logic can be managed.

Inter-thread Communication

Volatile Keyword

Variables marked volatile ensure visibility across threads. Without volatile, changes made by one thread may not be visible to others due to caching issues.

Synchronized Keyword

Used to protect critical sections. Only one thread can execute synchronized blocks or methods at a time, providing mutual exclusion and visibility guarantees.

Wait/Notify Pattern

The wait(), notify(), and notifyAll() methods allow coordination between threads. A thread calling wait() releases its lock and enters waiting state until notified.

Thread.join()

When thread A calls B.join(), A waits until B completes execution. Overloads support timeouts via join(long) or join(long, int).

Tags: java threading Concurrency multithreading JVM

Posted on Tue, 03 Nov 2026 16:28:54 +0000 by madhukar_garg