NBKRIST-JAVA HUB

My Expert Notes on Java Multi-threading

← Back to Multi-Threading Home

A detailed guide based on my experience and best practices.

In my journey, I learned that Multi-threading is crucial. It lets my programs handle multiple tasks at the same time (or concurrently), which is essential for performance. Every time I create a thread, I'm creating a small, independent unit of work.


1. The Two Methods I Use to Create Threads

No matter which method I choose, the actual work always goes inside the run() method. I always start a thread by calling start(), never run() directly.

A. Extending the Thread Class

This is the simplest way, but I try to avoid it unless necessary, as it consumes my single inheritance option.

  1. I create a class that extends java.lang.Thread.
  2. I override the public void run() method to put my task logic there.
  3. I create an object of my class and call the start() method on it.
// My first method to define a thread
class MyThread extends Thread {
    public void run() {
        System.out.println("Task 1: Running by extending Thread.");
    }
}

// Starting it up
public class Main {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start(); // Initiates the thread and calls run()
    }
}

B. Implementing the Runnable Interface 🌟 (My Preferred Method)

This is my expert judgment on the better option. Since Java doesn't support multiple inheritance, implementing Runnable keeps my class free to extend another superclass, which is invaluable for code structure and reuse.

  1. I create a class that **implements** the java.lang.Runnable interface.
  2. I **override** the public void run() method (the task).
  3. I create an object of the Thread class, passing my Runnable object to its constructor.
  4. I call the **start()** method on the **Thread object**.
// My task, separated from the Thread control
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Task 2: Running by implementing Runnable.");
    }
}

// Starting it up
public class Main {
    public static void main(String[] args) {
        MyRunnable task = new MyRunnable();
        Thread t2 = new Thread(task); // Thread object given the task
        t2.start(); // This is what I call to begin execution
    }
}

2. Understanding the Thread's Lifecycle

I view a thread as a living entity that moves through five distinct states from creation to destruction. I must understand these states to properly manage my programs.

State My Action / How it Gets There What it Means to Me
1. New (Born) When I use the new keyword (e.g., new Thread()). The thread object is created but is **not active** yet.
2. Runnable When I call the **start()** method. The thread is **ready to run** and is waiting for the CPU.
3. Running The OS scheduler gives the thread CPU time. The thread is **executing its run() method code**.
4. Blocked/Waiting/Timed Waiting When I call sleep(), or the thread is waiting for a **lock** (synchronized block) or another thread (join(), wait()). The thread is **temporarily inactive** and cannot run until its waiting condition is resolved.
5. Terminated (Dead) When the thread's **run() method completes naturally** or an exception is thrown. The thread is finished. I **cannot restart it**.
Thread States

Understanding these states helps me manage thread behavior and avoid common pitfalls like deadlocks or resource contention.


3. How I Manage and Control Threads (Safely)

⚠️ My Critical Safety Rule: Never Use Deprecated Methods!

I **never** use the old, unsafe methods like stop(), suspend(), or resume(). They can crash my application or cause severe deadlocks.