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.
0

Add a comment

Labels
Archive
Popular Posts
Popular Posts
  • Two JVM options are often used to tune JVM heap size: -Xmx for maximum heap size, and -Xms for initial heap size. Here are some common mi...
  • Simple enum . The ; after the last element is optional, when this is the end of enum definition. public enum Color { WHITE, BLACK, RED, ...
  • How to set project classpath in Eclipse and NetBeans are similar: just right-click the project name, choose Properties to bring up the Prope...
  • Let's say I need to spawn multiple threads to do the work, and continue to the next step only after all of them complete. I will need t...
  • This is a sample web.xml based on Servlet 2.5 (part of Java EE 5) that declares common elements. All top-level elements are optional, and c...
  • The default string value for java enum is its face value, or the element name. However, you can customize the string value by overriding toS...
  • Prior to JDK 6, we can check if a string is empty in 2 ways: if(s != null && s.length() == 0) if(("").equals(s)) Checking ...
  • When writing javadocs, IntelliJ automatically adds a closing tag for html elements. For instance, after typing <lt>, it automaticaly a...
  • StringBuilder was introduced in JDK 1.5. What's the difference between StringBuilder and StringBuffer? According to javadoc , StringBu...
  • With array, we can easily declare and initialize it at the same time: String[] favorites = new String[] {"EJB", "JPA", ...
Loading