The following java test app runs and terminates normally on Unix but hangs on Windows.
import java.util.concurrent.*;
public class ThreadPoolTest {
private static ExecutorService es = Executors.newCachedThreadPool();
public static void main(String args[]) {
es.submit(new Task("## running task 1..."));
es.submit(new Task("## running task 2..."));
System.out.println("## after tasks, in main");
}
private static class Task implements Runnable {
private String msg;
private Task(String s) {
this.msg = s;
}
@Override public void run() {
System.out.println(msg);
}
}
}
The direct reason for hanging is the 2 worker threads were returned to the cached thread pool after finishing the printing task, and stayed alive and idle indefinitely. Pooled threads by default are non-daemon threads. A Java program will terminate only after all non-daemon threads have terminated. Although the main thread in the above test app have finished its job, the Java process will not terminate due to the 2 live worker threads.
A couple of solutions and their pros and cons:
1, So shall we make the pooled threads daemon threads to fix it? Be careful here, since the Java process may just terminate prematurely, immediately after the main thread is done, but before daemon workers complete their work. Since they are daemon threads, their task status is totally ignored in exiting JVM.
If you really want to take the daemon approach, the application will need some logic to poll the stask status, and wait for their completion before exiting the main thread.
2, How about explicitly call
ExecutorService.shutdown()
method? After all, a task implementing app logic is not a daemon thread, and should be marked as such. Shutting down the thread pool (the actual type of ExecutorService in our example) makes more sense. But pay attention to all possible exit paths and make sure shutdown guaranteed in all paths, such as normal completion, throwable.
If the thread pool is used by various parts of a large application, how do you coordinate them such that shutdown is called only after all clients are finished? Compared to approach 1, additional coordiation (e.g., with wait/notify or CountDownLatch) is needed. To fix the hang using shutdown():
import java.util.concurrent.*;
public class ThreadPoolTest {
private static ExecutorService es = Executors.newScheduledThreadPool(2);
public static void main(String args[]) throws InterruptedException {
es.submit(new Task("# running task 1..."));
es.submit(new Task("# running task 2..."));
es.shutdown();
//block here for all tasks to finish before proceeding in the main thead.
//if you want to enforce the execution order. optional.
es.awaitTermination(1, TimeUnit.DAYS);
System.out.println("# after tasks, in main");
}
private static class Task implements Runnable {
private String msg;
private Task(String s) {
this.msg = s;
}
@Override public void run() {
try {
Thread.sleep(1000*30);
} catch (InterruptedException e) {
}
System.out.println(msg);
}
}
}
awaitTermination is totally optional. With it, you will see 2 task output before main thread output:
# running task 2...
# running task 1...
# after tasks, in main
Without it, main thread output comes before task output:
# after tasks, in main
# running task 2...
# running task 1...
3, Is there a default idle timeout for worker threads, so applications don't have to manage the shutdown? Yes, there is a default idle timeout 60 seconds for non-core threads. When creating a thread pool, you can specify the number of core threads, along with other parameters. By default core threads do not time out after becoming idle. But you can certainly make core threads subject to idle timeout by calling threadPool.allowCoreThreadsTimeout(true).
How does this apply to our test app? We created the thread pool by calling newCachedThreadPool() without passing any parameters. By default, the pool is created with 0 core threads and 60-second idle timeout. So any worker threads in the test app should be non-core and should automatically time out after 60 seconds. Yes, that is what happened on Mac OS or Linux. But on windows 7, the same program just hangs.
Do you still want to rely on its default idle timeout, which seems sensitive to OS?
4, Use Thread and Runnable class directly for simple use cases. When you only need threads, but don't need to maintain a pool, just directly use threads.
To rewrite the above program directly using java.lang.Thread:
public class ThreadTest {
public static void main(String args[]) throws InterruptedException {
Thread t1 = new Thread(new Task("## running task 1..."));
Thread t2 = new Thread(new Task("## running task 2..."));
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("## after tasks, in main");
}
private static class Task implements Runnable {
private String msg;
private Task(String s) {
this.msg = s;
}
@Override public void run() {
System.out.println(msg);
}
}
}
The 2 join calls are added to wait for the 2 child threads to finish processing before proceeding to the main thread execution.
View comments