Difference between Implementing Runnable and Extending Thread

Upasana | July 30, 2019 | 3 min read | 911 views


Recommendation

As per Object Oriented Programming, its better to use Runnable instead of extending thread, since we are not really specializing Thread’s behavior here. We are just interested in giving a unit of work (a runnable) to Thread.

So Composition (by implmenting Runnable and giving it to thread) makes more sense here rather than Inheritance (which is meant for specializing thread’s behavior by overriding its run method).

There are lots of other differences between these two approaches:

Implement Runnable vs Extending Thread
Extending Thread Implementing Runnable

Java does not allow multiple inheritance, so if you extend from thread, you can not extend from any other class.

By Implementing Runnable in our target class, we can still extend from other class.

It makes our class tightly coupled with Thread

By implementing Runnable, Task and Thread (executor) are loosely coupled. Consider here that Runnable object represents a Task.

Each time we execute the thread, a new object is created for both Thread and our class

Multiple threads can share the same Runnable object.

NA

Runnable Tasks can be used in Executor Framework as well.

No code reusability

Better code reusability

Exhausts systems resources quickly, so this approach is not scalable

Reuses system resources by using a shared threadpool, so lesser load on system resources, for example see Executors.newCachedThreadPool()

Not Recommended

Recommended approach

Example Code

Creating a runnable is easy, we just need to implement Runnable interface and provide work definition in run() method.

Runnable Example Source
public class DemoTask implements Runnable {

    @Override
    public void run() {
        System.out.println("Demo Task will run here.");
    }
}

class Worker {
    ExecutorService executorService = Executors.newSingleThreadExecutor();

    public Future execute(Runnable runnable) {
        return executorService.submit(runnable);
    }
}

public static void main(String[] args) {
    Worker worker = new Worker();   (1)
    worker.execute(() -> {
        System.out.println("this is an lambda work");
    });
}
1 Java 8 Lambda styled runnable argument passed to worker

Since Runnable is a FunctionalInterface with Single Abstract Method (SAM), we can use inline lambda expression inplace of Runnable Tasks.

Threads can be extended by overriding their run() method, as shown in the below code:

Extending Thread Example
public class ExtendsThread extends Thread {

    @Override
    public void run() {
        System.out.println("Demo Thread Task");
    }
}

class Worker {
    public void execute(Thread thread) {
        thread.start(); (1)
    }
}
1 We have to call start() on thread object, that will instruct os to create a new thread and start the execution. We can optionally wait for thread completion using thread.join() from caller thread.

How do you do it in Spring Framework/Spring Boot

If you are using Spring Framework, then its normal to create singleton beans for executor which are reused by all other components in the application. This approach makes sure that system resources (cpu and memory due to excessive context switching) are never exhausted at peak load, and the system remains responsive throughout.

Creating ThreadPoolTaskExecutor Singleton
@Bean(name = "taskExecutor")
public AsyncTaskExecutor taskExecutor() {
    final ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();   (1)
    taskExecutor.setCorePoolSize(10);
    taskExecutor.setMaxPoolSize(20);
    taskExecutor.setQueueCapacity(1000);
    taskExecutor.setThreadNamePrefix("tpasync-");
    taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
    return taskExecutor;
}
1 ThreadPoolTaskExecutor is a convenient thread pool provided by Spring Framework.
Important Note for Core Java usage

You should always prefer Executors, tasks and Streams to Threads. java.util.concurrent package contains an Executor Framework, which provides a flexible interface based task execution facility.

ExecutorService example usage
ExecutorService exec = Executors.newSingleThreadExecutor();
exec.execute(runnable);
exec.shutdown();

Many other things can be achieved using this Executor framework, for example:

  1. using get(), you can wait for a particular task to complete.

  2. using shutdown()' and `awaitTermination(), you can wait for executor to terminate.

  3. You can retrieve the results of tasks one by one as they become available using ExecutorCompletionService.

  4. You can schedule tasks to run at a particular time using ScheduledThreadPoolExecutor.


Buy my ebook for complete question bank

Most of these questions has been answered in my eBook "Cracking the Core Java Interview" updated on June 2018, that you can buy from this link:

Buy from Shunya (DRM Free PDF download with updates)

Top articles in this category:
  1. Difference between Callable and Runnable Interface
  2. What is difference between HashMap and HashSet
  3. What is difference between sleep() and wait() method in Java?
  4. Difference between HashMap and ConcurrentHashMap
  5. Difference between HashMap, LinkedHashMap and TreeMap
  6. What is difference between Vector and ArrayList, which one shall be preferred
  7. Difference between ExecutorService submit and execute method

Recommended books for interview preparation:

Find more on this topic:
Buy interview books

Java & Microservices interview refresher for experienced developers.