Java Runnable Interface
Last modified: April 13, 2025
The java.lang.Runnable
interface is a fundamental part of Java's
concurrency model. It represents a task that can be executed by a thread. Any
class whose instances need to be executed by a thread should implement this
interface.
The Runnable interface contains only one method: run
. When an
object implementing Runnable is used to create a thread, starting the thread
causes the object's run method to be called in that separately executing
thread. This provides a way to define what code should run in a thread.
Runnable Interface Definition
The Runnable interface is very simple, containing just one abstract method. This makes it a functional interface, which means it can be used with lambda expressions in Java 8 and later. Here's its definition:
@FunctionalInterface public interface Runnable { public abstract void run(); }
The code above shows the complete definition of the Runnable interface. The
@FunctionalInterface
annotation indicates it can be used with
lambda expressions. The interface requires implementing just the run method.
Basic Runnable Implementation
This example demonstrates the most basic way to implement the Runnable interface by creating a class that implements it. We then create a Thread object with our Runnable and start it.
package com.zetcode; class MyRunnable implements Runnable { @Override public void run() { System.out.println("Thread is running..."); for (int i = 1; i <= 5; i++) { System.out.println("Count: " + i); try { Thread.sleep(500); // Pause for 500 milliseconds } catch (InterruptedException e) { System.out.println("Thread interrupted"); } } System.out.println("Thread finished."); } } public class Main { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); thread.start(); System.out.println("Main thread continues to run..."); } }
In this example, we create a MyRunnable
class that implements
Runnable. The run
method contains the code that will execute in
the new thread. We create a Thread object with our Runnable instance and start
it with thread.start
. The main thread continues executing while
the new thread runs concurrently.
Runnable with Lambda Expression
With Java 8's lambda expressions, we can implement Runnable more concisely without creating a separate class. This is possible because Runnable is a functional interface with just one abstract method.
package com.zetcode; public class Main { public static void main(String[] args) { // Using lambda expression to implement Runnable Runnable runnable = () -> { System.out.println("Lambda thread running..."); for (int i = 0; i < 3; i++) { System.out.println("Working: " + i); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("Interrupted!"); } } System.out.println("Lambda thread done."); }; Thread thread = new Thread(runnable); thread.start(); System.out.println("Main thread doing other work..."); } }
This example shows how to use a lambda expression to implement Runnable. The
lambda provides a more concise way to define the thread's behavior without
creating a separate class. The syntax () -> { ... }
implements the
single run
method of the Runnable interface.
Multiple Threads with Runnable
We can create multiple threads using the same Runnable instance. This is useful when we want multiple threads to execute the same task. Each thread will have its own execution path.
package com.zetcode; class CounterRunnable implements Runnable { private final String name; public CounterRunnable(String name) { this.name = name; } @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println(name + ": " + i); try { Thread.sleep((long)(Math.random() * 1000)); } catch (InterruptedException e) { System.out.println(name + " interrupted"); } } System.out.println(name + " finished."); } } public class Main { public static void main(String[] args) { Runnable counter1 = new CounterRunnable("Counter 1"); Runnable counter2 = new CounterRunnable("Counter 2"); Thread thread1 = new Thread(counter1); Thread thread2 = new Thread(counter2); thread1.start(); thread2.start(); System.out.println("Main thread waiting for counters to finish..."); } }
In this example, we create two threads that share the same Runnable implementation but with different names. Each thread counts to 5 with random sleep intervals, showing how threads execute concurrently. The output will show interleaved counts from both threads, demonstrating concurrent execution.
Runnable with Anonymous Class
Before Java 8 introduced lambdas, anonymous classes were commonly used to implement Runnable concisely. This approach is still valid and useful when you need to access local variables from the enclosing method.
package com.zetcode; public class Main { public static void main(String[] args) { final String message = "Hello from thread!"; // Using anonymous class to implement Runnable Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println(message); for (int i = 0; i < 3; i++) { System.out.println("Processing step " + (i + 1)); try { Thread.sleep(800); } catch (InterruptedException e) { System.out.println("Processing interrupted"); } } System.out.println("Processing complete."); } }); thread.start(); System.out.println("Main thread continues execution..."); } }
This example demonstrates implementing Runnable using an anonymous class. The
anonymous class can access the message
variable because it's
declared final. This approach is more verbose than lambdas but necessary when
working with Java versions before 8 or when needing to override multiple
methods.
Runnable with Method Reference
Java 8 method references provide another concise way to implement Runnable when you have an existing method that matches the Runnable's run method signature. This is particularly useful for existing methods that take no parameters and return void.
package com.zetcode; public class Main { public static void workerMethod() { System.out.println("Worker method executing in thread: " + Thread.currentThread().getName()); for (int i = 0; i < 4; i++) { System.out.println("Working on task " + (i + 1)); try { Thread.sleep(600); } catch (InterruptedException e) { System.out.println("Work interrupted"); } } System.out.println("Worker method completed."); } public static void main(String[] args) { // Using method reference to implement Runnable Thread thread = new Thread(Main::workerMethod); thread.start(); System.out.println("Main thread doing other tasks..."); } }
In this example, we use a method reference Main::workerMethod
to
implement Runnable. The workerMethod
matches the signature of
run
(no parameters, void return), so it can be used directly as
a Runnable. This approach is clean and works well when you have existing methods
that fit the Runnable interface.
Runnable with Thread Pool
In real-world applications, it's often better to use thread pools rather than creating threads directly. The Executor framework provides thread pool implementations that can efficiently manage Runnable tasks.
package com.zetcode; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class Task implements Runnable { private final int taskId; public Task(int id) { this.taskId = id; } @Override public void run() { System.out.println("Starting task " + taskId + " in thread: " + Thread.currentThread().getName()); try { // Simulate work Thread.sleep(2000); } catch (InterruptedException e) { System.out.println("Task " + taskId + " interrupted"); } System.out.println("Completed task " + taskId); } } public class Main { public static void main(String[] args) { // Create a thread pool with 3 threads ExecutorService executor = Executors.newFixedThreadPool(3); // Submit 10 tasks to the pool for (int i = 1; i <= 10; i++) { Runnable task = new Task(i); executor.execute(task); } // Shutdown the executor when done executor.shutdown(); System.out.println("All tasks submitted to thread pool."); } }
This example demonstrates using Runnable with a thread pool. We create a pool of 3 threads and submit 10 tasks to it. The pool manages task execution efficiently, reusing threads rather than creating new ones for each task. Notice how only 3 tasks run concurrently, and others wait in the queue until a thread becomes available.
Runnable vs Thread
While both Runnable and Thread can be used to create threads, Runnable is generally preferred because it separates the task from the thread execution mechanism. This allows for more flexible designs where the same task can be executed in different ways.
package com.zetcode; // Approach 1: Extending Thread class MyThread extends Thread { @Override public void run() { System.out.println("Thread approach: " + getName()); } } // Approach 2: Implementing Runnable class MyRunnable implements Runnable { @Override public void run() { System.out.println("Runnable approach: " + Thread.currentThread().getName()); } } public class Main { public static void main(String[] args) { // Using Thread subclass MyThread thread1 = new MyThread(); thread1.start(); // Using Runnable Thread thread2 = new Thread(new MyRunnable()); thread2.start(); // Using lambda with Runnable Thread thread3 = new Thread(() -> System.out.println("Lambda Runnable: " + Thread.currentThread().getName())); thread3.start(); } }
This example compares the two approaches to creating threads in Java. The Runnable approach is more flexible as it allows your class to extend another class if needed. It also works better with modern Java features like lambdas and thread pools. The Thread approach is simpler but less flexible as Java doesn't support multiple inheritance.
Source
Java Runnable Interface Documentation
In this tutorial, we've explored the Java Runnable interface in depth with practical examples. Runnable is fundamental to Java's concurrency model and understanding it is essential for writing multithreaded applications in Java.
Author
List all Java tutorials.