Concurrency is a crucial concept when it comes to improving the performance of applications by executing multiple tasks simultaneously. While there are several ways to create threads in Java, one of the most commonly used interfaces is Runnable
.
Runnable
is a functional interface in Java that represents a task that can be executed concurrently by a thread. It is part of the java.lang
package and has a single method:
1 |
public void run(); |
When we implement the Runnable
interface, we define the run()
method, which contains the code that will be executed when the thread starts. Unlike extending the Thread
class, using the Runnable
interface allows our class to extend another class while still being able to run code in a separate thread.
In Java, we can create a thread in two ways:
- By extending
Thread
class: This approach allows our class to directly inherit from theThread
class, and override therun()
method to define the task. - By implementing
Runnable
interface: This approach is considered a better practice, as it allows our class to extend other classes while still using concurrency.
Implementation
Using Runnable interface
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class RunnableExample { public static void main(String[] args) { // Step 1: Create a Runnable implementation Runnable task = new Runnable() { @Override public void run() { System.out.println("Task is running in a separate thread."); } }; // Step 2: Create a Thread object and pass the Runnable Thread thread = new Thread(task); // Step 3: Start the thread thread.start(); // Optionally, you can print something in the main thread as well System.out.println("Main thread is running."); } } //Output Main thread is running. Task is running in a separate thread. |
Using Runnable with Executor Service
In modern Java applications, it is often more efficient to use the Executor framework for managing threads. Here’s an example of how to use Runnable
with the ExecutorService to run tasks asynchronously:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(2); // Define tasks Runnable task1 = () -> System.out.println("Task 1 is running"); Runnable task2 = () -> System.out.println("Task 2 is running"); // Submit tasks to the executor executor.submit(task1); executor.submit(task2); // Shutdown executor executor.shutdown(); } } |
In this example, we create a thread pool with a fixed size of two threads. We then submit two tasks (task1
and task2
) to the pool, and they are executed concurrently by the available threads.
Advantages of Runnable
- Improved Code Structure: By separating task logic from thread management, you can organize your code better.
- Multi-tasking: The
Runnable
interface allows you to define multiple tasks that can be executed in parallel. - Thread Pooling:
Runnable
is frequently used with Java’s Executor framework, which manages a pool of threads and helps manage the lifecycle of threads more efficiently. - Separation of concerns: Implementing
Runnable
lets your class focus on defining tasks (in therun()
method) without being tied to a specific thread management strategy. - Reusability: You can pass the same
Runnable
object to multiple threads, allowing you to reuse tasks without needing to create new thread subclasses. - Decoupling: The task logic is decoupled from the thread management, promoting cleaner and more flexible code.