Java Secrets: Using ExecutorService to run many background tasks.

Overview

Java has had support for thread pools for a many years, but using them is still black art for many. Part of the problem is that misusing a thread pool will make a program run slower not faster and cause the program to crash or even hang the system.

I often see people trying to use a queue, or wait/notify and thread(s) when an ExecutorService does all this for you.

Choosing the right thread pool

For different situations, different pools are useful. Sometimes it makes sense to have more than one to take advantage of their different characteristics.
Thread pool typeHow to createusesdisadvantages
Single threaded poolExecutors. newSingleThreadExecutor();Useful to event processing e.g. logs always performs tasks in order and minimises overheadOnly one thread
Fixed size poolExecutors. newFixedThreadPool (n_threads);Useful for using many cores, possibly all cores in your systemAlways has the same number of threads even if not used.
Cached thread poolExecutors. newCachedThreadPool();A good alternative to creating new threads for each task by re-cycling threadsThe number of threads is unlimited and incorrect use can bring a system to its knees.
Single threaded scheduling pool Executors. newSingleThreadScheduledExecutor (n_threads);Useful to event processing some with delaysOnly one thread
Multi threaded scheduling poolExecutors. newScheduledThreadPool();Fixed size pool for recurring and delay eventsAlways has the maximum number threads

Performance tips

The cost of a creating task in a pool can be significant and the smaller you make the task, the greater the overhead to real work being done.

Performing a loop across multiple threads

I have added an example program for the use of Executor.

The first example is single threaded. This should be the baseline of any performance test as there is no point using multiple threads if the task will be slower.

The second example shows that even with an optimal number of threads, making the tasks too small will make the task take much longer.

The third example uses as little tasks as possible which will still keep all the cores busy. This is usually optimal for a CPU intensive task.

The examples print
Single threaded: Time to geomean 1,000,000 values was 7.163 msecs. 
Too many tasks: Time to geomean 1,000,000 values was 601.613 msecs. 
One task per thread: Time to geomean 1,000,000 values was 2.349 msecs. 

Code: ExecutorTestMain

Comments

Popular posts from this blog

Low Latency Microservices, A Retrospective

Unusual Java: StackTrace Extends Throwable

System wide unique nanosecond timestamps