Runnable

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:

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 the Thread class, and override the run() 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

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:

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 the run() 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.