Blog

Maximizing Java Concurrency with Runnable: A Guide to Flexible Task Execution

Using the Runnable interface allows you to define a task that can be executed by different threads or even used with advanced concurrency mechanisms provided by the Java Concurrency API, such as ExecutorService. This makes your task implementation more versatile and reusable across various execution strategies.

Example with Runnable

Let’s say we have a simple Runnable task that prints out numbers from 1 to 5 with a delay between each number. We will then execute this task using different strategies: directly with a Thread and with an ExecutorService.

Defining the Runnable Task

class NumberPrinter implements Runnable {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Number: " + i);
            try {
                Thread.sleep(1000); // Sleep for 1 second
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // Set the interrupt flag
                System.out.println("Interrupted!");
                break;
            }
        }
    }
}

Execution Strategy 1: Using Thread

public class DirectThreadExample {
    public static void main(String[] args) {
        NumberPrinter task = new NumberPrinter();
        Thread thread = new Thread(task);
        thread.start();
        try {
            thread.join(); // Wait for the thread to complete
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Execution Strategy 2: Using ExecutorService

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ExecutorServiceExample {
    public static void main(String[] args) {
        NumberPrinter task = new NumberPrinter();
        ExecutorService executor = Executors.newSingleThreadExecutor();

        executor.execute(task); // Execute the task

        executor.shutdown(); // Shutdown the executor
        try {
            if (!executor.awaitTermination(8000, TimeUnit.MILLISECONDS)) {
                executor.shutdownNow(); // Force shutdown if task didn't finish
            }
        } catch (InterruptedException e) {
            executor.shutdownNow(); // Force shutdown on interrupt
        }
    }
}

Explanation

  • Versatility of Runnable: As shown, the same NumberPrinter task is executed in two different ways without any change to its implementation. This demonstrates the flexibility of using Runnable; the task doesn’t need to know about its execution mechanism.
  • Direct Thread Usage: The first strategy directly uses a Thread to execute the Runnable task. This approach is straightforward and gives you direct control over thread management, such as starting and joining the thread.
  • Using ExecutorService: The second strategy uses an ExecutorService to manage task execution. This is part of Java’s Concurrency API and provides a higher-level abstraction for executing concurrent tasks. It handles thread management for you, including pooling and lifecycle management, and can be more efficient and flexible for running multiple tasks.

This example illustrates how implementing tasks as Runnable allows you to separate the definition of the task from its execution mechanism, enabling you to choose or change how the task is executed without modifying the task itself. This design provides significant flexibility, making your code more reusable and adaptable to different execution contexts.

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over