Java Concurrency - ExecutorService

ExecutorService allows users to run asynchronous tasks.

ExecutorService

ExecutorService is an Interface in java.util.concurrent package. It extends Executor interface.

Executor defines a single method execute(Runnable command). It is used to execute the submitted Runnable task.

Creating ExecutorService

Executors class provides factory methods to create ExecutorService. You can have a ExectorService with a single thread or a thread pool.

1
2
ExecutorService executor = Executors.newSingleThreadExecutor();
ExecutorService executor = Executors.newFixedThreadPool(5);

Running Tasks

execute(Runnable)

execute(Runnable) method is inherited from Executor interface. It is used to execute a runnable task at some time in the future. The return type is void.

1
2
3
4
5
6
7
8
9
10
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i = 0; i < 5; i++) {
int finalI = i;
Runnable myRunnable = () -> {
System.out.printf("Running task %d using thread %s\n", finalI, Thread.currentThread().getName());
};
executor.execute(myRunnable);
}
TimeUnit.SECONDS.sleep(1);
executor.shutdown();

Sample output:

1
2
3
4
5
Running task 0 using thread pool-1-thread-1
Running task 1 using thread pool-1-thread-2
Running task 3 using thread pool-1-thread-2
Running task 4 using thread pool-1-thread-2
Running task 2 using thread pool-1-thread-1

submit(Callable)

submit method can take a Runnable or Callable as argument. submit() method executes the Runnable or Callable and returns a Future. If you pass a Runnable to submit method, Future.get() will return null. If you pass a Callable to submit method, Future.get() will return the actual result of the execution.

submit(Callable) example usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ExecutorService executor = Executors.newFixedThreadPool(2);
List<Future<String>> results = new ArrayList<>();
for (int i = 0; i < 5; i++) {
int finalI = i;
Callable<String> myCallable = () -> {
System.out.printf("Running task %d using thread %s\n", finalI, Thread.currentThread().getName());
return "Task " + Integer.toString(finalI);
};
Future<String> result = executor.submit(myCallable);
results.add(result);
}
for (Future<String> result : results) {
System.out.println(result.get(1000, TimeUnit.MILLISECONDS));
}
executor.shutdown();

Sample output:

1
2
3
4
5
6
7
8
9
10
Running task 1 using thread pool-1-thread-2
Running task 0 using thread pool-1-thread-1
Running task 2 using thread pool-1-thread-1
Running task 3 using thread pool-1-thread-2
Task 0
Task 1
Task 2
Task 3
Running task 4 using thread pool-1-thread-1
Task 4

Run All tasks with invokeAll(Collection)

invoke all tasks will execute a collection of Callable tasks and return a list of Futures.

Example to run a task 100 times using 20 threads.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ExecutorService executor = Executors.newFixedThreadPool(20);
List<Callable<String>> myCallables = Collections.nCopies(1000, () -> {
// run task...
return "Task 1";
});
List<Future<String>> futures = executor.invokeAll(myCallables);
futures.forEach(f -> {
try {
String result = f.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
executor.shutdown();

Shutdown

ExecutorService will keep the JVM running if you don’t shut it down.

shutdown() method will initiate an orderly shutdown. Once Shutdown() is called, no new tasks will be accepted. Tasks submitted before shutdown() call will not be cancelled. Once all threads are finished, ExecutorService will shutdown.

1
executorService.shutdown();

shutdownNow() attemps to shutdown ExecutorService immediately and return a list of tasks that were awaiting execution. There is no guarantees all executing tasks wll be stopped.

1
executorService.shutdownNow();

awaitTermination(long timeout, TimeUnit unit) will block until all tasks have completed execution after a shutdown request. Usually called after shutdown() or shutdownNow().

Example code

1
2
3
4
5
6
7
8
executorService.shutdown();
try {
if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}

ScheduledExecutorService

ScheduledExecutorService is an Interface that extends ExecutorService. It can schedule tasks to run after a given delay, or execute periodically.

use schedule() method to schedule a task to run after 3 seconds

1
2
3
4
5
6
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable myRunnable = () -> {
System.out.println(Thread.currentThread().getName() + " - Hello");
};
executor.schedule(myRunnable, 3, TimeUnit.SECONDS);
executor.shutdown();

use scheduleAtFixedRate() method to schedule at a fixed rate of 2 seconds.

1
2
3
4
5
6
7
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable myRunnable = () -> {
System.out.println(Thread.currentThread().getName() + " - Hello");
};
executor.scheduleAtFixedRate(myRunnable, 0, 2, TimeUnit.SECONDS);
TimeUnit.SECONDS.sleep(5);
executor.shutdown();

Callable Interface

Callable is very similar to Runnable. It is a task that returns a result while Runnable‘s run() method returns void.

Callable Interface Definition

1
2
3
4
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}

Create a Callable

1
2
3
4
Callable<String> myCallable = () -> {
TimeUnit.SECONDS.sleep(1);
return "Task1";
};

Future Interface

A Future represents the result of an asynchronous computation.

Future<V> Interface is in java.util.concurrent package.

1
2
3
4
5
6
7
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

get() method

get() method will block the current thread and waits for the execution to be finished. You can also add timeout to prevent long running threads. e.g. future.get(100, TimeUnit.SECONDS).

1
2
String result = mycallableFuture.get();
System.out.println(result);

get() throws InterruptedException and ExecutionException. They are both checked exceptions.

cancel() method

attempts to cancel execution of this task. You can set mayInterruptIfRunning parameter to be true so that an InterruptedException is throwed for the executing task.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<String> myCallable = () -> {
try {
System.out.println("task started");
TimeUnit.SECONDS.sleep(3);
System.out.println("task finished");
} catch( InterruptedException e) {
e.printStackTrace();
}
return "Task1";
};
Future<String> future = executor.submit(myCallable);
TimeUnit.SECONDS.sleep(1);
boolean cancelled = future.cancel(true);
System.out.println("cancelled: " + cancelled);
executor.shutdown();

output:

1
2
3
4
5
6
7
8
9
10
11
task started
cancelled: true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.example.App.lambda$main$0(App.java:15)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

Reference