In Java, there're some ways to run your code in a multi-threaded environment such as inheriting the Thread class, implementing the Runnable interface, and implementing the Callable interface

Let's walk through this tutorial to see the examples in details

Inherit the Thread class

You can inherit your class from the Thread class and override the run() method with the code you want to be executed by multiple threads

Let's define an example class as below


The ThreadCounter class extends the Thread class and override the run() method to implement a counter from 1 to 100


In ThreadTest, we created an instance of ThreadCounter and execute it in an ExecutorService with a fixed thread pool

Inheriting the Thread class is not a recommended way if you are only planning to override the run() method and not enhancing or modifying the default behaviour of the Thread class. Composition is preferred over inheritance in most cases. So let's try the composition way

Implement the Runnable interface

The Runnable interface is designed for running in multi-threaded environment. The Thread class actually is a Runnable implementation

Let's define a class that implementing the Runnable interface as the following


In the RunnableCounter class, we overrode the run() method of the Runnable interface to provide the code we want to run in multi-threading environment


We also created an instance of RunnableCounter and execute it in an ExecutorService with a fixed thread pool

In most cases, as pointed out in the previous section, implementing the Runnable interface is preferred to inheriting the Thread class

Runnable, so Thread, can not return the result and cannot throw a checked exception, that's why we have to pass the AtomicInteger instance into ThreadCounter and RunnableCounter constructors

Object result = executorService.submit(new RunnableCounter(counter)).get();  
assertThat(result).isNull();  
assertThat(counter.get()).isEqualTo(100);  

There's a better and more official way to get the result

Implement the Callable interface

The Callable interface is similar to the Runnable but return a result and may throw an exception

Let's define a class that implementing the Callable interface as the following


In the CallableCounter class, we overrode the call() method of the Callable interface to provide the code we want to run in multi-threading environment

Unlike the run() method of Runnable, call() can throw an Exception


In CallableTest, we wrote a unit test case to execute the CallableCounter in multiple threads. We can get the result returned from the Callable with the get() method of the Future class instance returned by the ExecutorService

AtomicInteger result = executorService.submit(new CallableCounter()).get();  
assertThat(result.get()).isEqualTo(100);  


Conclusion

In this tutorial, we learned how to creating a new thread of execution, the difference between Thread, Runnable and Callable and when to use them. You can find the full source code here