ExecutorService

ExecutorService is an interface in the JDK. An implementation of the interface can execute a Runnable or Callable class in an asynchronous way. The interface only defines the API for the implementation and does not require that the invocation is asynchronous but, in reality, that is the main point implementing such a service. Invoking the run method of a Runnable interface in a synchronous way is simply calling a method. We do not need a special class for that.

The Runnable interface defines one run method. It has no arguments returns no value and does not throw any exception. The Callable interface is parameterized and the only method it defines, call, has no argument but returns a generic value and may also throw Exception. In our code, we will implement Runnable if we just want to run something, and Callable when we want to return something. Both of these interfaces are functional interfaces, therefore, they are good candidates to be implemented using lambda.

To have an instance of an implementation of an ExecutorService, we can use the utility class Executors. Many times when there is an XYZ interface in the JDK, there can be an XYZs (plural) utility class that provides factory for the implementations of the interface. If we want to start the t1 task many times, we can do so without creating a new Thread. We should use the following executor service:

public class ThreadIntermingling { 
static class MyThread implements Runnable {
private final String name;

MyThread(String name) {
this.name = name;
}

@Override
public void run() {
for (int i = 1; i < 1000; i++) {
System.out.print(name + " " + i + ", ");
}
}
}
public static void main(String[] args)
throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(2);
Runnable t1 = new MyThread("t1");
Runnable t2 = new MyThread("t2");
Future<?> f1 = es.submit(t1);
Future<?> f2 = es.submit(t2);
System.out.print("started ");
f1.get();
f2.get();
System.out.println();
f1 = es.submit(t1);
es.shutdown();
}
}

This time, we do not get any exception. Instead, the t1 task runs second time. In this example, we are using a fixed size thread pool that has two Threads. As we want to start only two threads simultaneously, it is enough. There are implementations that grow and shrink the size of the pool dynamically. Fixed size pool should be used when we want to limit the number of the threads or we know from some other information source the number of the a-priory threads. In this case, it is a good experiment to change the size of the pool to one and see that the second task will not start in this case until the first one finishes. The service will not have another thread for t2 and will have to wait until the one and only Thread in the pool is freed.

When we submit the task to the service, it returns even if the task cannot currently be executed. The tasks are put in a queue and will start execution as soon as there is enough resource to start them. The submit method returns a Future object, as we can see in the preceding sample.

It is like a service ticket. You bring your car to the repair mechanic, and you get a ticket. You are not required to stay there until the car is fixed, but at any time, you can ask if the car is ready. All you need is the ticket. You can also decide to wait until the car is ready. A Future object is also something like that. You do not get the value that you need. It will be calculated asynchronously. However, there is a Future promise that it will be there and your ticket to access the object you need is the Future object.

When you have a Future object, you can call the isDone method to see if it is ready. You can start waiting for it to call get with, or without, some timeout. You can also cancel the task executing it, but in that case, the outcome may be questionable. Just like, in case of your car, if you decide to cancel the task, you may get back your car with the motor disassembled. Similarly, cancelling a task that is not prepared for it may lead to resource loss, opened and inaccessible database connection (this is a painful memory for me, even after 10 years), or just a garbled unusable object. Prepare your tasks to be cancelled or do not cancel them.

In the preceding example, there is no return value for Future because we submitted a Runnable object and not a Callable one. In that case the value passed to the Future is not to be used. It is usually null, but that is nothing to lean on.

The final and most important thing that many developers miss, even me, after not writing multithread Java API using code for years, is shutting down the ExecutorService. The ExecutorService is created and it has Thread elements. The JVM stops when all non-daemon threads are stopped. It ain't over till the fat lady sings.

A thread is a daemon thread if it was set to be daemon (invoking setDaemon(true)) before it was started. A thread is automatically daemon of the starting thread is a daemon thread. Daemon threads are stopped by the JVM when all other threads are finished and the JVM wants to finish. Some of the threads the JVM executes itself are daemon threads, but it is likely that there is no practical use of creating daemon threads in an application program.

Not shutting down the service simply prevents the JVM from stopping. The code will hang after the main method finishes. To tell the ExecutorService that there is no need for the threads it has, we will have to shutdown the service. The call will only start the shutdown and return immediately. In this case, we do not want to wait. The JVM does anyway. If we need to wait, we will have to call awaitTermination.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.181.252