4.8 KiB
Threads and Synchronisation
Executor
The Executor
interface is used to execute tasks. It is a generic interface that can be used to execute any kind of task. The Executor
interface has only one method:
public interface Executor {
void execute(Runnable command);
}
The execute
method takes a Runnable
object as a parameter. The Runnable
interface is a functional interface that has only one method. Executors internally use a thread pool to execute the tasks. The execute
method is non-blocking. It returns immediately after submitting the task to the thread pool. The execute
method is used to execute tasks that do not return a result.
A thread pool is a collection of threads that are used to execute tasks. Instead of creating a new thread for each task, a thread pool reuses the existing threads to execute the tasks. This improves the performance of the application.
The Executor
interface has a method called newCachedThreadPool
that returns an ExecutorService
object. The ExecutorService
interface extends the Executor
interface. The ExecutorService
interface has methods to execute tasks that return a result. The ExecutorService
interface also has methods to shutdown the thread pool.
To run a task using the Executor
interface, we can use the newCachedThreadPool
method to create an ExecutorService
object. The newCachedThreadPool
method returns an ExecutorService
object that uses a thread pool with a variable number of threads. The newCachedThreadPool
method creates a new thread for each task if there are no idle threads in the thread pool. If there is an idle thread in the thread pool, the newCachedThreadPool
method reuses the idle thread to execute the task. The newCachedThreadPool
method returns an ExecutorService
object that uses a thread pool with a variable number of threads.
Executor executorService = Executors.newCachedThreadPool();
executorService.execute(() -> System.out.println("Hello World"));
Callable and Future
Runnables do not return a result. If we want to execute a task that returns a result, we can use the Callable
interface. The Callable
interface is a functional interface that has only one method:
public interface Callable<V> {
V call() throws Exception;
}
The call
method returns a result of type V
. The call
method can throw an exception. The Callable
interface is used to execute tasks that return a result.
For instance we can use the Callable
interface to execute a task that returns the sum of two numbers:
Callable<Integer> sumTask = () -> 2 + 3;
In order to execute a task that returns a result, we can use the submit
method of the ExecutorService
interface. The submit
method takes a Callable
object as a parameter. The submit
method returns a Future
object. The Future
interface has a method called get
that returns the result of the task. The get
method is a blocking method. It waits until the task is completed and then returns the result of the task.
ExecutorService executorService = Executors.newCachedThreadPool();
Future<Integer> future = executorService.submit(() -> 2 + 3);
Integer result = future.get();
Futures can be used to cancel tasks. The Future
interface has a method called cancel
that can be used to cancel a task. The cancel
method takes a boolean parameter. If the boolean parameter is true
, the task is cancelled even if the task is already running. If the boolean parameter is false
, the task is cancelled only if the task is not running.
ExecutorService executorService = Executors.newCachedThreadPool();
Future<Integer> future = executorService.submit(() -> 2 + 3);
future.cancel(false);
Synchronisation
Whenever we have multiple threads that access the same resource, we need to make sure that the threads do not interfere with each other. This is called synchronisation.
Synchronisation can be seen in the adder and subtractor example. The adder and subtractor threads access the same counter variable. If the adder and subtractor threads do not synchronise, the counter variable can be in an inconsistent state.
- Create a count class that has a count variable.
- Create two different classes
Adder
andSubtractor
. - Accept a count object in the constructor of both the classes.
- In
Adder
, iterate from 1 to 100 and increment the count variable by 1 on each iteration. - In
Subtractor
, iterate from 1 to 100 and decrement the count variable by 1 on each iteration. - Print the final value of the count variable.
- What would the ideal value of the count variable be?
- What is the actual value of the count variable?
- Try to add some delay in the
Adder
andSubtractor
classes using inspiration from the code below. What is the value of the count variable now?