Java Secrets: Using ExecutorService to run many background tasks.
OverviewJava 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 poolFor different situations, different pools are useful. Sometimes it makes sense to have more than one to take advantage of their different characteristics.
|Thread pool type||How to create||uses||disadvantages|
|Single threaded pool||Executors. newSingleThreadExecutor();||Useful to event processing e.g. logs always performs tasks in order and minimises overhead||Only one thread|
|Fixed size pool||Executors. newFixedThreadPool (n_threads);||Useful for using many cores, possibly all cores in your system||Always has the same number of threads even if not used.|
|Cached thread pool||Executors. newCachedThreadPool();||A good alternative to creating new threads for each task by re-cycling threads||The 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 delays||Only one thread|
|Multi threaded scheduling pool||Executors. newScheduledThreadPool();||Fixed size pool for recurring and delay events||Always has the maximum number threads|
Performance tipsThe 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 threadsI 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.