Multi Threading Tutorial

Java Multithreading Tutorial: Thread Class, Runnable, and Methods (2026)

Java Multithreading Tutorial: Thread Class, Runnable, and Methods

What is Multithreading?

Multithreading is a part of a process that has the ability to perform more than one job concurrently.

The main method is the main thread in Java, where your program execution starts. But the main thread executes tasks one by one. If you want to execute multiple tasks at the same time, you need to create multiple threads from your main method.

Using multithreading, you can achieve multitasking.

There are two ways to achieve multitasking:

  1. Multiprocessor based
  2. Multithread based

Both are part of the operating system. So you can request a thread be created through the thread scheduler of the OS.

Java supports multithreading but not multiprocessing for these reasons:

Process Thread
Process is a group of threads. Thread is part of a process or a sub-process.
Process is heavyweight. Thread is lightweight.
Process has its own memory area. Thread shares the process memory.
Inter-process communication is very expensive. Inter-thread communication is cheap.
Process affects the main memory. Thread affects the process memory.

Note: In Java, a process is provided by default. You do not need to worry about the process. You just create a thread and perform your task.

Thread Scheduler

The thread scheduler is responsible for maintaining the sequence of thread execution — deciding which thread should be given preference over others.

The scheduling depends on the algorithm of the scheduler, such as preemptive scheduling and time slicing with round robin.

Preemptive scheduling: The highest-priority task executes until it enters the waiting or dead state, or a higher-priority task comes into existence.

Time slicing: A task executes for a predefined slice of time and then re-enters the pool of ready tasks.

Why Multithreading?

  • Multithreading uses the full CPU power.
  • You can execute multiple tasks at the same time.
  • You save execution time.
  • You reduce response time.
  • You can serve multiple clients at the same time.

When to use Multithreading?

Multithreading is used when you have limited resources.

Example: A single shopping website can be accessed by multiple clients at the same time. That shopping site is a single resource (a limited resource).

What is a Thread?

  • A thread is a lightweight sub-process.
  • Every thread has its own stack where you define separate thread tasks.
  • You can create your own threads to perform multiple tasks at the same time.

There are two ways to create a thread:

  1. By extending the Thread class
  2. By implementing the Runnable interface

1. By extending Thread class

The Thread class is a predefined class in Java that exists in the java.lang package.

Use the Thread class when you want to use only thread resources, because in this case you cannot extend any other class.

The Thread class also implements the Runnable interface.

You need to override the run() method to define a task, and call the start() method to create a thread through the thread scheduler.

The Thread class provides the following methods:

Return type Method with description
Thread Thread.currentThread() — returns the currently running thread object.
long getId() — returns the identifier of this thread.
String getName() — returns the name of this thread.
int getPriority() — returns the priority of this thread, between 1 and 10.
Thread.State getState() — returns the state of this thread: NEW, RUNNABLE, BLOCKED, WAITING, TERMINATED, and so on.
ThreadGroup getThreadGroup() — returns the ThreadGroup object of this thread.
boolean isAlive() — returns whether the thread is alive or terminated.
boolean isDaemon() — checks whether this thread is a daemon (service) thread.
boolean isInterrupted() — tests whether this thread has been interrupted.
void join() — waits for this thread to die (terminate).
void run() — defines the task executed by this thread.
void setDaemon(boolean on) — marks this thread as a daemon or user thread.
void setName(String name) — sets the name of this thread.
void setPriority(int priority) — sets the priority between 1 and 10, or use constants like MAX_PRIORITY, MIN_PRIORITY, and NORM_PRIORITY.
void start() — begins execution. The JVM calls the run() method of this thread.
void Thread.sleep(long milliseconds) — causes the currently executing thread to sleep for the specified number of milliseconds.
void Thread.yield() — a hint to the scheduler that the current thread is willing to yield its use of a processor.

Thread Examples

Hello World program

public class Test extends Thread {

    @Override
    public void run() {
        System.out.println("Hello World");
    }

    public static void main(String[] args) {
        new Test().start();
    }
}

Result: Hello World

How to get the current thread

public class Test {

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}

Result: main

How to check whether a thread is created

public class Test extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new Test().start();
    }
}

Result: Thread-0

If you do not call start()

public class Test extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new Test().run();
    }
}

Result: main

Note: If you call the run() method directly, no new thread is created. The task executes on the main thread.

If you call start() twice

public class Test extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Test t = new Test();
        t.start();
        t.start();
    }
}

Result: Thread-0
IllegalThreadStateException

Note: If you call start() twice on the same object, you get an IllegalThreadStateException.

How to create multiple threads

public class Test extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Test t1 = new Test();
        t1.start();
        Test t2 = new Test();
        t2.start();
    }
}

Result: Thread-0
Thread-1

2. By implementing Runnable interface

The Runnable interface is a predefined interface in Java that exists in the java.lang package.

Runnable has only one method: run().

Use the Runnable interface when you need to extend another class and also use thread resources.

You implement the Runnable interface in your class and override the run() method to define the task.

How to create a thread using Runnable

public class Test implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Test t = new Test();

        // Here the implementing class is called the runnable target.
        // The runnable target is associated with a Thread object.
        Thread tt = new Thread(t);
        tt.start();
    }
}

Result: Thread-0

How to create multiple threads with Runnable

public class Test implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Test t = new Test();

        Thread tt0 = new Thread(t);
        tt0.start();

        Thread tt1 = new Thread(t);
        tt1.start();
    }
}

Result: Thread-0
Thread-1

Note: You can use either way to create a thread, based on your requirements.

Where to go next

You now know what a thread is, how the scheduler decides which one runs, how to create threads with both the Thread class and Runnable, and the methods you will use most often.

If you remember one thing, remember this: calling start() creates a new thread. Calling run() does not. That one difference is the entire point of multithreading.

If you want to go deeper, try these next steps:

  • Add Thread.sleep(1000) inside run() and watch the output order change.
  • Set different priorities on two threads and observe how the scheduler behaves.
  • Use join() to make the main thread wait for a worker thread to finish.
  • Move on to synchronization. The moment two threads share a variable, you need to understand race conditions and the synchronized keyword.

No comments: