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