Topic 96 of 119 11. Multithreading in Java Interactive Tutorial

Creating Threads in Java: Extending Thread vs Implementing Runnable

9 mins read Last updated: 22 Aug 2026 31,608 views

Direct Answer & Core Concept

Creating a thread in Java means defining a task that can run independently from the main flow of the program. Java provides several concurrency APIs, but two traditional approaches are extending the Thread class and implementing the Runnable interface.

Understanding these two approaches is important because they teach the basic relationship between a thread and the task performed by that thread. In modern Java applications, Runnable and higher-level concurrency tools are often preferred when the task should be separated from thread management.

Detailed Explanation & Mechanics

What Is a Thread in Java?

A thread is a lightweight unit of execution inside a Java program. A Java application can have multiple threads running different tasks concurrently.

For example, one thread might process user input while another performs background work.

Main.java
Run Online
public class Main {
    public static void main(String[] args) {
        System.out.println("Main thread is running");
    }
}

Every Java application starts with a main thread that executes the main() method. Additional threads can be created when the program needs concurrent execution.


Two Traditional Ways to Create a Thread

Java provides two commonly taught approaches:

  1. Extend the Thread class

  2. Implement the Runnable interface

Both can create a thread that executes code inside its run() method.

Basic idea

Main.java
Run Online
Thread | +---- run() | +---- Task code

The important distinction is that Thread represents the execution mechanism, while Runnable represents a task that can be executed by a thread.


1. Creating a Thread by Extending Thread

The first approach is to create a class that extends Java's Thread class.

You then override the run() method and place the thread's work inside it.

Example

Main.java
Run Online
class MyThread extends Thread {
    @Override public void run() {
        System.out.println("Thread is running");
    }
}public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

Here, MyThread inherits from Thread. The run() method contains the code that should execute when the thread starts.

The start() method is important because it starts a new thread and eventually causes run() to execute on that new thread.


Understanding start() vs run()

One of the most important concepts when creating Java threads is the difference between calling start() and calling run() directly.

Correct way

Main.java
Run Online
MyThread thread = new MyThread();
thread.start();

Calling start() asks the JVM to start a new thread.

Not the same

Calling run() directly is simply a normal method call. It does not create a new thread.

Example

Main.java
Run Online
class MyThread extends Thread {
    @Override public void run() {
        System.out.println("Running in: " + Thread.currentThread().getName());
    }
}public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.run();
    }
}

In this case, run() executes as part of the current thread rather than starting a new thread.

So remember:

Main.java
Run Online
start() → starts a new threadrun() → executes the method normally when called directly

2. Creating a Thread by Implementing Runnable

The second approach is to implement the Runnable interface.

Runnable represents a task that can be executed by a thread.

Example

Main.java
Run Online
class MyTask implements Runnable {
    @Override public void run() {
        System.out.println("Task is running");
    }
}public class Main {
    public static void main(String[] args) {
        MyTask task = new MyTask();
        Thread thread = new Thread(task);
        thread.start();
    }
}

Here, MyTask does not become a thread itself. It defines the work that a thread should perform.

The Thread object is responsible for running that task.


How Runnable Works

The relationship can be visualized like this:

Main.java
Run Online
Runnable | | contains v Task | | passed to v Thread | | start() vExecution

This separation is one of the main advantages of the Runnable approach.

The task describes what should be done, while the thread describes where/how the task is executed.


Thread vs Runnable Example

Here are the two approaches side by side.

Extending Thread

Main.java
Run Online
class MyThread extends Thread {
    @Override public void run() {
        System.out.println("Running task");
    }
}MyThread thread = new MyThread();
thread.start();

Implementing Runnable

Main.java
Run Online
class MyTask implements Runnable {
    @Override public void run() {
        System.out.println("Running task");
    }
}Thread thread = new Thread(new MyTask());
thread.start();

Both approaches can execute the task on a separate thread.


Passing a Runnable to Thread

The Thread class provides constructors that accept a Runnable.

For example:

Main.java
Run Online
Runnable task = new Runnable() {
    @Override public void run() {
        System.out.println("Hello from the task");
    }
};
Thread thread = new Thread(task);
thread.start();

You can also write this more compactly using a lambda expression.

Main.java
Run Online
Runnable task =() -> {
    System.out.println("Hello from the task");
};
Thread thread = new Thread(task);
thread.start();

This style is common in modern Java code.


Runnable With Lambda Expressions

Because Runnable is a functional interface, it can be represented using a lambda expression.

Instead of:

Main.java
Run Online
Runnable task = new Runnable() {
    @Override public void run() {
        System.out.println("Processing data...");
    }
};

You can write:

Main.java
Run Online
Runnable task =() -> {
    System.out.println("Processing data...");
};

Or even:

Main.java
Run Online
Thread thread = new Thread(() -> System.out.println("Processing data..."));
thread.start();

This is useful for small tasks where creating a separate class would add unnecessary code.


Extending Thread: Advantages

Extending Thread is simple and easy to understand for beginners.

Simple syntax

The thread and its task are combined into one class.

Main.java
Run Online
class Worker extends Thread {
    @Override public void run() {
        System.out.println("Worker running");
    }
}

Direct access to Thread methods

Because the class itself extends Thread, it can directly use methods such as:

Main.java
Run Online
start();
sleep(1000);
getName();
interrupt();

However, this convenience does not necessarily make it the best design for larger applications.


Extending Thread: Limitation

Java does not support multiple class inheritance.

For example:

Main.java
Run Online
class MyThread extends Thread {
}

Because MyThread already extends Thread, it cannot extend another class.

This can become a problem if the class needs to inherit from another superclass.

For example:

Main.java
Run Online
class Employee {
    // Employee functionality
}

You cannot write:

Main.java
Run Online
class Worker extends Employee, Thread {
}

Java does not allow this form of multiple inheritance.

This is one reason implementing Runnable can provide more flexibility.


Runnable: Advantages

Implementing Runnable separates the task from the thread.

Main.java
Run Online
class FileProcessor implements Runnable {
    @Override public void run() {
        System.out.println("Processing file");
    }
}

The class can still extend another class if necessary.

Main.java
Run Online
class Employee {
    // Employee functionality
}class FileProcessor extends Employee implements Runnable {
    @Override public void run() {
        System.out.println("Processing file");
    }
}

This works because Java allows a class to extend one class and implement interfaces.


Runnable Encourages Task Separation

One of the biggest conceptual advantages of Runnable is separating task logic from thread management.

For example:

Main.java
Run Online
class EmailTask implements Runnable {
    @Override public void run() {
        System.out.println("Sending email...");
    }
}

The task does not need to know how the thread is managed.

Another part of the application can decide how to execute it:

Main.java
Run Online
Runnable task = new EmailTask();
Thread thread = new Thread(task);
thread.start();

The same task can also be used with higher-level concurrency APIs.


Reusing the Same Runnable Task

A Runnable object represents a task, so it can potentially be supplied to different Thread objects.

Main.java
Run Online
Runnable task =() -> {
    System.out.println( "Running in " + Thread.currentThread().getName());
};
Thread thread1 = new Thread(task, "Worker-1");
Thread thread2 = new Thread(task, "Worker-2");
thread1.start();
thread2.start();

Each thread executes the same task logic.

The output may look like:

Main.java
Run Online
Running in Worker-1Running in Worker-2

The exact order of thread output is not guaranteed.


Example: Multiple Threads

Let's create several worker threads.

Main.java
Run Online
class Worker implements Runnable {
    private final String workerName;
    public Worker(String workerName) {
        this.workerName = workerName;
    } @Override public void run() {
        for (int i = 1;
        i <= 3;
        i++) {
            System.out.println( workerName + " - step " + i);
        }
    }
}public class Main {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Worker("Worker A"));
        Thread t2 = new Thread(new Worker("Worker B"));
        t1.start();
        t2.start();
    }
}

Because both threads execute concurrently, their output may be interleaved.

For example:

Main.java
Run Online
Worker A - step 1Worker B - step 1Worker A - step 2Worker B - step 2Worker B - step 3Worker A - step 3

Another execution may produce a different order.

Do not write multithreaded code assuming a particular scheduling order unless synchronization or another coordination mechanism guarantees it.


Thread Names

Giving threads meaningful names makes debugging easier.

Main.java
Run Online
Runnable task =() -> {
    System.out.println( Thread.currentThread().getName());
};
Thread thread = new Thread(task, "File-Worker");
thread.start();

You can retrieve the current thread with:

And retrieve its name with:

Main.java
Run Online
Thread.currentThread().getName()

Meaningful thread names are particularly useful when reading logs from applications with many concurrent operations.


Can a Thread Be Started Twice?

A Thread object can only be started once.

This is invalid:

Main.java
Run Online
Thread thread = new Thread(() -> System.out.println("Hello"));
thread.start();
thread.start();

Attempting to start the same Thread instance again results in IllegalThreadStateException.

If you need another thread, create another Thread object.

Main.java
Run Online
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();

The same Runnable task can be used with separate thread objects.


Constructor With Runnable

You can create a thread and provide its task directly.

Main.java
Run Online
Runnable task =() -> {
    System.out.println("Task executing");
};
Thread thread = new Thread(task);
thread.start();

You can also give the thread a name:

Main.java
Run Online
Thread thread = new Thread( task, "Background-Worker");
thread.start();

This makes the relationship between task and execution mechanism clear.


Extending Thread vs Implementing Runnable

The two approaches can be compared conceptually:

FeatureExtending ThreadImplementing Runnable
Defines taskrun() in subclassrun() in implementation
Creates threadClass itself is a ThreadThread object executes task
Can extend another classNoYes
Separates task from threadLessBetter
Can use lambda directlyNoYes
Task reuseLess flexibleMore flexible
Beginner simplicityVery simpleSimple
Common design choiceUseful for learning/specialized casesOften preferred for task-based design

The key difference is not that one magically creates “more concurrency” than the other. Both ultimately use a Thread to perform the work.


Which Approach Should You Learn First?

For understanding Java's thread fundamentals, it is useful to learn both.

Start with Thread because it makes the basic lifecycle easy to visualize:

Main.java
Run Online
create Thread↓start()↓run()↓thread completes

Then learn Runnable because it introduces the important separation between a task and the thread that executes it.

For application design, Runnable is often a better abstraction when you have a task that should be independent from the mechanism used to execute it.


Runnable and ExecutorService

In production applications, you will often move beyond manually creating threads.

For example:

Main.java
Run Online
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> {
    System.out.println("Task running");
});
executor.shutdown();

Here, the application submits a task to an executor rather than manually creating a new Thread for every task.

This approach provides higher-level management of threads and task execution.

For many server-side applications, executors are more practical than creating large numbers of raw threads manually.


Thread vs Runnable vs ExecutorService

Think of these concepts at different levels:

Main.java
Run Online
Thread↓Low-level execution mechanismRunnable↓A task that can be executedExecutorService↓Higher-level task/thread management

A useful mental model is:

Main.java
Run Online
WHAT should happen?↓RunnableHOW should it execute?↓Thread / ExecutorService

This separation becomes increasingly important as applications become larger.


Common Mistakes

Mistake 1: Calling run() Instead of start()

This does not start a new thread.

Use:


Mistake 2: Starting the Same Thread Twice

Main.java
Run Online
thread.start();
thread.start();

A Thread instance cannot be restarted.

Create a new thread instead.


Mistake 3: Assuming Thread Order

Main.java
Run Online
thread1.start();
thread2.start();

This does not guarantee that thread1 will finish before thread2.

Thread scheduling is controlled by the JVM and operating system.


Mistake 4: Creating Too Many Raw Threads

Creating a huge number of threads manually can create unnecessary resource and scheduling overhead.

For larger applications, consider higher-level concurrency tools such as ExecutorService or virtual threads where appropriate.


Modern Java: Virtual Threads

Modern Java also provides virtual threads, which are lightweight threads designed to make high-concurrency applications easier to build.

For example:

Main.java
Run Online
Thread.startVirtualThread(() -> {
    System.out.println("Running in a virtual thread");
});

Virtual threads do not replace the need to understand Thread and Runnable. Instead, they provide another execution model for suitable workloads.

They are especially useful when applications have many concurrent tasks that spend significant time waiting on blocking operations.


Practical Example: Download Task

Suppose an application needs to process a download task.

Using Runnable

Main.java
Run Online
class DownloadTask implements Runnable {
    private final String fileName;
    public DownloadTask(String fileName) {
        this.fileName = fileName;
    } @Override public void run() {
        System.out.println( "Downloading " + fileName);
    }
}public class Main {
    public static void main(String[] args) {
        Runnable task = new DownloadTask("report.pdf");
        Thread thread = new Thread(task, "Download-Worker");
        thread.start();
    }
}

The DownloadTask describes the work, while Thread provides the execution mechanism.

This separation makes the design easier to adapt later if the application moves to an executor or another concurrency mechanism.


When to Use Each Approach

Extending Thread

Use it when:

  • You specifically need a specialized Thread subclass.

  • You are learning the fundamentals of Java threads.

  • Combining the task and thread behavior is intentional.

Implementing Runnable

Use it when:

  • You want to separate task logic from thread management.

  • Your class already needs to extend another class.

  • You want to use a lambda expression.

  • You expect the task to be submitted to an executor.

  • You want the task abstraction to remain reusable.

ExecutorService

Consider it when:

  • The application has many tasks.

  • You need controlled thread management.

  • You want thread pools.

  • You want to submit tasks without manually creating a new thread for every operation.


Simple Mental Model

Remember this distinction:

Main.java
Run Online
extends Thread↓"My class IS a thread."implements Runnable↓"My class IS a task."Thread(task)↓"This thread executes the task."

This simple model helps explain why Runnable is commonly preferred when designing task-oriented concurrent code.


Frequently Asked Questions

Is Runnable a thread?

No. Runnable represents a task. A Thread can execute that task.

Does calling run() create a thread?

No. Calling run() directly is a normal method call. Calling start() begins execution on a new thread.

Can a class extend Thread and another class?

No. Java allows a class to extend only one class.

Can a class extend another class and implement Runnable?

Yes.

Main.java
Run Online
class Worker extends Employee implements Runnable {
    @Override public void run() {
        System.out.println("Working...");
    }
}

Can the same Runnable be used by multiple threads?

Yes.

Main.java
Run Online
Runnable task =() -> System.out.println("Working");
new Thread(task).start();
new Thread(task).start();

The task's behavior may execute concurrently on separate threads. If the task accesses shared mutable state, appropriate synchronization or other concurrency controls may be necessary.

Can a Thread be started twice?

No. A Thread instance can only be started once.

Which is better, Thread or Runnable?

They solve slightly different design problems. Thread combines thread behavior with the class, while Runnable represents a task separately. For task-oriented design, Runnable provides greater separation and flexibility.

Is Runnable still useful with modern Java?

Yes. Runnable remains a fundamental task abstraction and is used with threads, executors, and other concurrency APIs.

Runnable Code Example: Creating Threads in Java

Java 17+
Main.java
Run Online
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadCreationDemo {
    public static void main(String[] args) throws Exception {
        //
        1. Runnable with Lambda Runnable task =() -> System.out.println("Runnable running on " + Thread.currentThread().getName());
        new Thread(task).start();
        //
        2. Callable with Return Value Callable<Integer> callableTask =() -> 42 * 2;
        FutureTask<Integer> future = new FutureTask<>(callableTask);
        new Thread(future).start();
        System.out.println("Callable Result: " + future.get());
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1

Why is implementing Runnable preferred over extending Thread in Java?

Implementing Runnable preserves single class inheritance for domain models, decouples task logic, and allows direct submission to ExecutorService thread pools.
Q2

What is the difference between Runnable and Callable?

Runnable's run() returns void and cannot throw checked exceptions; Callable's call() returns a parameterized value and can throw checked exceptions.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Use ExecutorService.submit(Callable) to execute asynchronous background tasks and retrieve computed values via Future or CompletableFuture.