Surprising Behavior of a Cached Thread Pool
While working on a small JavaFX application, I noticed some
strange behavior. When I closed all windows, the application did not stop
immediately, even though all threads were done with their work. It became even
more mysterious. After a while the application would shut down correctly. What
was going on?
As it turned out, the reason was an ExecutorService that
used a cached thread pool. Caching and reusing threads is a good idea, in
particular if you want to execute many small tasks. Creating threads is
expensive and by caching and reusing them, you can avoid the overhead.
What I was not aware of though: the ExecutorService does not
remove idle threads immediately, but keeps them for about 60 seconds before
clearing them. Further analysis showed that idle threads in the
ExecutorService, which were waiting to be removed, prevented the immediate
shutdown of my application. The following code example condenses the problem:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreads {
public static void
main(String[] args) {
final
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(() -> System.out.println("Hello World"));
}
}
One would expect that the application ends immediately, but
it waits about 60 seconds before it returns.
The Solution
In my case the solution was easy. I declared the threads in
the thread pool as daemon threads. Daemon threads do not prevent the application
from exiting, even if they are still running. You can achieve this by declaring
a thread factory and passing it to Executors.newCachedThreadPool() as you can
see in the following example. This ensures that all threads of the
ExecutorService are daemon threads. In addition, it also allows to name the
threads, which is tremendously useful during development.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class ImprovedCachedThreads {
public static void
main(String[] args) {
final
ThreadFactory threadFactory = runnable -> {
final
Thread thread = new Thread(runnable, "HelloWorldThread");
thread.setDaemon(true);
return
thread;
};
final
ExecutorService executor = Executors.newCachedThreadPool(threadFactory);
executor.submit(() -> System.out.println("Hello World"));
}
}
Unfortunately the example above will still not work
correctly. Daemon threads really stop immediately, even if they still have work
to do. In the example above, it is very likely, that you do not see the output
“Hello World” at all, because the application exits before it can print the
output. In my application the problematic threads do not do anything important
and I am able to kill them immediately, but that is certainly not always the
case. If you need to be sure that a daemon thread finishes its work, you have
to test it explicitly, e.g. by using a CountDownLatch.
Another solution is to create the ThreadPoolExecutor
directly instead of using one of the factories in Executors. This allows you to
modify the keepAliveTime, which defines how long idle threads are kept. But be
careful: if the keepAliveTime becomes too small, you end up creating and
destroying too many
threads.[Source]-https://netopyr.com/2017/03/13/surprising-behavior-of-cached-thread-pool/
We provide best java training institutes in mumbai, navi mumbai. We have industry experienced trainers and provide
hands on practice. Basic to advanced modules are covered in training sessions.
Comments
Post a Comment