Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Java Concurrency Fundamentals: Thread Lifecycle and Implementation Strategies

Tech 1

Thread Lifecycle States

During concurrent execution, multiple active threads share the same processor by rapidly alternating CPU time slices. This creates the illusion of simultaneous execution, though at any exact moment, only a single thread holds the CPU execution privilege—a concept known as concurrency.

A thread transitions through five distinct states during its lifecycle:

  1. New: The thread instance is created but not yet started. Instantiating a Thread object places it in this initial state.
  2. Runnable: Invoking the start() method transitions the thread from New to Runnable. The thread is now eligible for CPU scheduling and execution, waiting for its assigned time slice.
  3. Running: The thread obtains CPU resources and executes its run() logic. A thread can only enter this state from the Runnable pool.
  4. Blocked: The thread voluntarily relinquishes CPU control or is prevented from acquiring it, pausing execution. It must return to the Runnable state before it can run again. Blockage occurs in three primary scenarios:
    • Waiting Block: Triggered by invoking wait(), forcing the thread to pause until another thread signals completion via notify() or notifyAll().
    • Synchronization Block: The thread attempts to acquire a synchronized lock currently held by another thread, halting progress until the lock is released.
    • General Block: Caused by operations like sleep(), join(), or blocking I/O requests. The thread resumes to Runnable upon timeout expiration, thread termination, or I/O completion.
  5. Terminated: The thread completes its run() method naturally or exits abruptly due to an unhandled exception, ending its lifecycle.

The mechanisms governing these states rely on core language components: the Object class provides wait() and notify() for inter-thread communication; the Thread class supplies operational methods like sleep() and interrupt(); and the synchronized keyword enforces lock-based access control.

Implementing Multithreading

Concurrent behavior is primarily achieved through two approaches: subclassing Thread or implementing the Runnable interface.

The Runnable interface defines a single abstract method, run(), serving as the execution entry point:

public interface Runnable {
    public abstract void run();
}

Since Thread itself implements Runnable, both strategies ultimately rely on overriding this method. However, Runnable is generally preferred. Because Java restricts single inheritance, extending Thread limits the subclass's flexibility. Implementing Runnable, on the other hand, leaves the class free to extend another parent class while still supporting concurrent execution. Crucially, Runnable facilitates efficient resource sharing; multiple threads can operate on a single Runnable instance, accessing the same underlying data, whereas extending Thread typically requires separate instances with isolated state.

Approach 1: Extending the Thread Class

Subclassing Thread isolates state per thread instance. In the following logic, each thread maintains its own distinct workload counter.

class TaskRunner extends Thread {
    private int loadCapacity = 5;

    @Override
    public void run() {
        while (loadCapacity > 0) {
            System.out.println(getName() + " processing unit: " + loadCapacity--);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        TaskRunner runnerA = new TaskRunner();
        TaskRunner runnerB = new TaskRunner();
        TaskRunner runnerC = new TaskRunner();

        runnerA.start();
        runnerB.start();
        runnerC.start();
    }
}

Because runnerA, runnerB, and runnerC are separate objects, each possesses an independent loadCapacity of 5. Execution results in 15 total processing units being handled, distributed independently across the three threads.

Approach 2: Implementing the Runnable Interface

Implementing Runnable allows multiple threads to share a single resource object. Here, one SharedTask instance is distributed across three thread wrappers.

class SharedTask implements Runnable {
    private int sharedLoad = 5;

    @Override
    public void run() {
        while (sharedLoad > 0) {
            System.out.println(Thread.currentThread().getName() + " executing unit: " + sharedLoad--);
        }
    }
}

public class RunnableDemo {
    public static void main(String[] args) {
        SharedTask task = new SharedTask();

        Thread workerA = new Thread(task);
        Thread workerB = new Thread(task);
        Thread workerC = new Thread(task);

        workerA.start();
        workerB.start();
        workerC.start();
    }
}

All three worker threads reference the same task object. Consequently, they jointly deplete the sharedLoad counter. The program completes after exactly 5 processing units are executed collectively, rather than 15.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.