Java Runnable Interface
Runnable describes a task through one run() method. It does not create or start a thread by itself. We can hand the task to a Thread or an executor and keep the task logic separate from the way it is scheduled.
What is the Runnable Interface?
Runnable is a functional interface that represents a task that can be executed by a thread.
It contains a single abstract method:
public interface Runnable {
void run();
}The run() method contains the code that should be executed by the thread.
However, calling run() directly does not create a new thread. To execute the task on a separate thread, we create a Thread object and call its start() method.
Why Use Runnable?
Java provides two common ways to create a thread:
- Extend the Thread class
- Implement the Runnable interface
Using Runnable is often preferred when the class represents a task rather than being a specialized type of thread.
It also allows the class to extend another class because Java does not support multiple class inheritance.
Creating a Thread Using Runnable
There are two main steps:
- Implement the Runnable interface.
- Pass the Runnable object to a Thread object and call start().
Example
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("Task is running...");
}
}
public class HelloWorld {
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread = new Thread(task);
thread.start();
}
}Output
Task is running...The run() method contains the task, while the Thread object is responsible for executing that task on a separate thread.
How Runnable Works
Consider this statement:
Thread thread = new Thread(task);Here:
- task is an object of a class implementing Runnable.
- The Thread object receives that task.
- Calling start() starts a new thread.
- The new thread eventually invokes the run() method.
The flow can be represented as:
Runnable object
↓
Thread
↓
start()
↓
New thread is created
↓
run() executesrun() vs start()
This is one of the most important concepts when working with threads.
Calling run() directly:
task.run();This is simply a normal method call.
It does not create a new thread.
Calling start():
thread.start();This requests the JVM to start a new thread, and that thread executes the run() method.
Example
class MyTask implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class HelloWorld {
public static void main(String[] args) {
MyTask task = new MyTask();
task.run(); // normal method call
Thread thread = new Thread(task);
thread.start(); // starts a new thread
}
}The first call executes on the current thread, while the second executes through a newly started thread.
Runnable with Lambda Expression
Runnable is a functional interface because it contains only one abstract method.
Therefore, we can use a lambda expression to create a Runnable.
Example
Runnable task = () -> {
System.out.println("Task is running...");
};
Thread thread = new Thread(task);
thread.start();This is a shorter alternative to creating a separate class.
Runnable with Lambda Expression Directly
We can also pass the lambda expression directly to the Thread constructor.
Thread thread = new Thread(() -> {
System.out.println("Task is running...");
});
thread.start();This approach is commonly used when the task is small and does not need to be reused.
Runnable Using Anonymous Class
Before lambda expressions were introduced in Java 8, an anonymous class was commonly used.
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Task is running...");
}
};
Thread thread = new Thread(task);
thread.start();Lambda expressions provide a more concise way to write the same type of task.
Runnable and Thread Class
There is an important difference between implementing Runnable and extending Thread.
Using Thread
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running...");
}
}Using Runnable
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("Task is running...");
}
}With Runnable, the task and the thread are separate concepts.
Runnable → represents the task
Thread → executes the taskThis separation is useful when designing multithreaded applications.
Runnable Allows Another Class to Be Extended
Java does not allow a class to extend two classes.
For example, this is not allowed:
class MyClass extends ClassA, Thread {
}However, a class can extend another class and implement Runnable.
class MyTask extends Employee implements Runnable {
@Override
public void run() {
System.out.println("Task is running...");
}
}This is one of the important advantages of using Runnable.
The class can inherit from another class while still defining a task that can be executed by a thread.
Running the Same Runnable with Multiple Threads
A single Runnable object can be passed to multiple Thread objects.
class MyTask implements Runnable {
@Override
public void run() {
System.out.println(
Thread.currentThread().getName() + " is running"
);
}
}
public class HelloWorld {
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread1 = new Thread(task, "Thread-1");
Thread thread2 = new Thread(task, "Thread-2");
thread1.start();
thread2.start();
}
}Both threads execute the same run() method.
The output order is not guaranteed because thread scheduling is controlled by the JVM and operating system.
Possible output:
Thread-1 is running
Thread-2 is runningThe order may also be reversed.
Runnable Does Not Represent a Thread
It is important to understand that a Runnable object itself is not a thread.
For example:
Runnable task = () -> {
System.out.println("Running task");
};The Runnable represents the work to be performed.
A Thread is responsible for running that work:
Thread thread = new Thread(task);
thread.start();So:
Runnable → What should be done
Thread → Executes the workRunnable and Thread Constructor
The Thread class provides constructors that can accept a Runnable.
For example:
Thread thread = new Thread(task);We can also provide a thread name:
Thread thread = new Thread(task, "Worker-Thread");Example:
Runnable task = () -> {
System.out.println(
Thread.currentThread().getName()
);
};
Thread thread = new Thread(task, "Worker-Thread");
thread.start();Output
Worker-ThreadRunnable with Thread Methods
Once a Runnable is passed to a Thread, we can use the methods provided by the Thread class.
For example:
Runnable task = () -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Task completed");
};
Thread thread = new Thread(task);
thread.start();Here:
- Runnable defines the task.
- Thread manages the execution.
- sleep() temporarily pauses the executing thread.
- start() begins thread execution.
Runnable vs Thread
| Feature | Runnable | Thread |
|---|---|---|
| Type | Interface | Class |
| Package | java.lang | java.lang |
| Main method | run() | run() |
| Creates a thread by itself | No | start() creates/starts the thread |
| Can extend another class | Yes | No, because it already extends Thread |
| Represents | Task/work | Thread of execution |
| Lambda support | Yes | Not directly as a replacement for a Runnable task |
| Recommended for task definition | Yes | Usually separate task from execution |
Runnable vs Callable
Runnable and Callable are both used to represent tasks, but they have an important difference.
Runnable
Runnable task = () -> {
System.out.println("Task executed");
};A Runnable:
- does not return a result
- cannot declare checked exceptions from run()
- uses the run() method
Callable
Callable<Integer> task = () -> {
return 100;
};A Callable:
- can return a result
- can throw checked exceptions
- uses the call() method
Callable is commonly used with ExecutorService and Future.
Runnable with ExecutorService
In modern Java applications, tasks are often submitted to an ExecutorService rather than creating threads manually.
Example:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HelloWorld {
public static void main(String[] args) {
ExecutorService executor =
Executors.newFixedThreadPool(2);
Runnable task = () -> {
System.out.println(
Thread.currentThread().getName()
+ " is executing"
);
};
executor.submit(task);
executor.shutdown();
}
}Here:
Runnable
↓
ExecutorService
↓
Thread Pool
↓
Worker Thread
↓
run()This approach is useful when an application needs to execute many tasks without manually creating a new thread for every task.
Important Points About Runnable
- Runnable is an interface in java.lang.
- It contains one abstract method: run().
- Runnable is a functional interface.
- It can be used with lambda expressions.
- A Runnable represents a task, not a thread.
- Calling run() directly does not start a new thread.
- Calling Thread.start() starts a new thread that executes the task.
- A class implementing Runnable can still extend another class.
- The same Runnable object can be passed to multiple threads.
- Runnable does not return a result.
- Callable can be used when a task needs to return a result.
- Runnable tasks can also be submitted to an ExecutorService.
Common Mistake: Calling run() Instead of start()
Incorrect when you want concurrent execution:
Runnable task = () -> {
System.out.println("Task running");
};
Thread thread = new Thread(task);
thread.run();Calling run() directly does not start a new thread.
Correct:
thread.start();The key difference is:
run() → normal method call
start() → starts a new threadCommon Mistake: Starting the Same Thread Twice
A Thread object can be started only once.
Thread thread = new Thread(task);
thread.start();
thread.start(); // ErrorThe second start() call results in an IllegalThreadStateException.
If the same task needs to be executed by multiple threads, create separate Thread objects:
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();When Should You Use Runnable?
Runnable is a good choice when:
- You want to separate a task from the thread that executes it.
- Your class already extends another class.
- You want to use lambda expressions for simple tasks.
- The task does not need to return a result.
- You want to submit tasks to an ExecutorService.
For modern applications, Runnable is commonly used together with executors and thread pools rather than creating large numbers of threads manually.
Key Takeaways
Runnable
↓
Represents a task
↓
Implement run()
↓
Pass Runnable to Thread
↓
Call start()
↓
Thread executes run()The most important thing to remember is:
Runnable defines the work, while Thread provides the execution mechanism.
For example:
Runnable task = () -> {
System.out.println("Hello from thread");
};
Thread thread = new Thread(task);
thread.start();Here, Runnable defines what the thread should do, and Thread determines where and how that task is executed.
See you in next page....🙂