Java Thread Lifecycle

Understanding the thread lifecycle is essential for mastering Java concurrency. While we have already seen the basic states of a thread, this chapter goes deeper into how threads transition between states, how the thread scheduler works, and what happens during context switching.

A thread does not simply start and stop; it moves through multiple states based on system conditions, synchronization, and scheduling decisions. These transitions are what define the behavior and performance of multithreaded applications.

State Transitions

A thread in Java moves through different states during its lifecycle. These states are not isolated—they are connected through well-defined transitions triggered by method calls, resource availability, or system events.

NEW → RUNNABLE

A thread begins in the NEW state when it is created but not yet started. Once start() is invoked, the thread transitions to the RUNNABLE state.
        Thread t = new Thread(() -> {
            System.out.println("Running...");
        });
        t.start();
At this point, the thread is ready to execute, but it depends on the scheduler to actually run.

RUNNABLE → RUNNING (Implicit)

Java does not explicitly define a RUNNING state, but when a thread is actively executing, it is considered to be running within the RUNNABLE state.

The transition from ready-to-run to actively running is controlled by the thread scheduler.

RUNNABLE → BLOCKED

A thread enters the BLOCKED state when it attempts to acquire a lock that is already held by another thread.
        synchronized (this) {
            // thread needs lock to enter here  
        }
The thread remains blocked until the lock becomes available.

RUNNABLE → WAITING

A thread enters the WAITING state when it waits indefinitely for another thread to perform a specific action.
        synchronized (obj) {
            obj.wait();
        }
It will remain in this state until it receives a notification via notify() or notifyAll().

RUNNABLE → TIMED_WAITING

A thread enters the TIMED_WAITING state when it waits for a specified duration.
Thread.sleep(1000); 
After the timeout expires, the thread returns to the RUNNABLE state.

WAITING/TIMED_WAITING → RUNNABLE

A waiting thread becomes runnable again when:

- It is notified (notify() / notifyAll())
- The timeout expires
- It is interrupted

BLOCKED → RUNNABLE

A blocked thread transitions back to RUNNABLE when it successfully acquires the required lock.

RUNNABLE → TERMINATED

A thread enters the TERMINATED state when its run() method completes execution.
        Thread t = new Thread(() -> {
            System.out.println("Task completed");
        });
        t.start(); 
Once terminated, a thread cannot be restarted.

Thread Scheduler

The thread scheduler is a crucial component responsible for deciding which thread gets CPU time. It is part of the underlying operating system, not the JVM itself, although the JVM interacts closely with it.

When multiple threads are in the RUNNABLE state, the scheduler selects one of them for execution based on various factors such as priority, availability, and scheduling policy.

Scheduling Strategies

Two common scheduling approaches are used:
1. Preemptive Scheduling
In this approach, the scheduler can interrupt a running thread and allocate CPU time to another thread. Most modern operating systems use preemptive scheduling.
2. Time-Slicing
Each thread is given a small time slice (quantum) to execute. After its time expires, the CPU switches to another thread.

Thread Priority

Java allows threads to have priorities ranging from Thread.MIN_PRIORITY (1) to Thread.MAX_PRIORITY (10).
        Thread t = new Thread(() -> {
            System.out.println("Running with priority");
        });
        t.setPriority(Thread.MAX_PRIORITY);
        t.start();
However, thread priority is only a hint to the scheduler and may not always be strictly followed depending on the OS.

The exact behavior of thread scheduling is not guaranteed and may vary across platforms. Therefore, developers should never rely on thread scheduling order for correctness.

Context Switching

Context switching is the process of saving the state of one thread and restoring the state of another so that multiple threads can share a single CPU effectively.

What Happens During Context Switch?

When the CPU switches from one thread to another, it must:

- Save the current thread's state (registers, program counter, stack)
- Load the next thread's state
- Resume execution from where the new thread left off

This allows multiple threads to appear as if they are running simultaneously, even on a single-core processor.

Performance Impact

Context switching is not free—it has overhead. Excessive context switching can degrade performance because the CPU spends more time switching between threads than doing actual work.
Example Scenario
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName());
            }).start();
        }
In this example, multiple threads compete for CPU time. The scheduler rapidly switches between them, creating the illusion of parallel execution.

Key Insights

- Threads constantly move between states based on conditions and system behavior
- The scheduler controls execution but is not predictable
- Context switching enables concurrency but introduces overhead
- Efficient thread management is critical for performance

A deep understanding of thread state transitions, scheduling, and context switching is essential for building high-performance concurrent applications. These concepts explain why threads behave the way they do and help developers avoid common pitfalls such as performance bottlenecks and unpredictable execution.

In the next chapter, we will explore core thread methods such as sleep(), join(), and interrupt(), which give developers fine-grained control over thread execution.
Nagesh Chauhan
Nagesh Chauhan
Principal Engineer | Java · Spring Boot · Python · Microservices · AI/ML

Principal Engineer with 14+ years of experience in designing scalable systems using Java, Spring Boot, and Python. Specialized in microservices architecture, system design, and machine learning.

Share this Article

💬 Comments

Join the discussion