gstackoverflow
gstackoverflow

Reputation: 37016

How to manage pool thread termination in ThreadPoolExecutor?

Quote from concurrency in practice:

To set an UncaughtExceptionHandler for pool threads, provide a ThreadFactory to the ThreadPoolExecutor constructor. (As with all thread manipulation, only the thread's owner should change its UncaughtExceptionHandler.) The standard thread pools allow an uncaught task exception to terminate the pool thread, but use a try-finally block to be notified when this happens so the thread can be replaced. Without an uncaught exception handler or other failure notification mechanism, tasks can appear to fail silently, which can be very confusing. If you want to be notified when a task fails due to an exception so that you can take some task-specific recovery action, either wrap the task with a Runnable or Callable that catches the exception or override the afterExecute hook in THReadPoolExecutor.

Book doesn't provide any examples how to achieve this.

Can you show this technique ?

P.S.

I tried to write code sample:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread();
            thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {
                    e.printStackTrace();
                }
            });
            System.out.println("created thread with id " + Thread.currentThread().getId());
            return thread;
        }
    });
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getId() + " started");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getId() + " termination");
            throw new RuntimeException();

        }
    };
    threadPoolExecutor.submit(runnable);
    threadPoolExecutor.submit(runnable);
    threadPoolExecutor.submit(runnable);
    threadPoolExecutor.submit(runnable);
}

but this output

created thread with id 1
created thread with id 1

always.

Upvotes: 0

Views: 2316

Answers (1)

Holger
Holger

Reputation: 298539

There are several errors in your code. First, you must pass the Runnable passed to your ThreadFactory to the created thread, otherwise, you leave a broken thread not executing any tasks. Second, you are printing the id of Thread.currentThread() in your factory, which is obviously not the freshly created thread. That’s why it prints an id of 1 two times.

Still, after fixing these errors, you won’t see uncaught exceptions. The reason is that while the thread pool executor behaves as stated, the submit methods wrap your Runnable into a FutureTask that catches all exceptions on its own, so that they can be reported when calling get().

At this point, we have to remember that there is a queue of arbitrary Runnables, which don’t have to be FutureTask instances. So uncaught exceptions are still possible, e.g. when we enqueue runnables directly via execute.

So the fixed example:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 1,
                TimeUnit.MINUTES, new LinkedBlockingQueue<>(), r -> {
                    Thread thread = new Thread(r);
                    thread.setUncaughtExceptionHandler((t, e) -> {
                        synchronized(System.out) {
                            System.out.println("Uncaught exception in "+t.getId());
                            e.printStackTrace(System.out);
                        }
                    });
                    System.out.println("created thread with id " + thread.getId());
                    return thread;
        });
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getId() + " started");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getId() + " termination");
            throw new RuntimeException();
            };
        threadPoolExecutor.execute(runnable);
        threadPoolExecutor.execute(runnable);
        threadPoolExecutor.execute(runnable);
        threadPoolExecutor.execute(runnable);
        threadPoolExecutor.shutdown();
    }
}

will print

created thread with id 11
created thread with id 12
11 started
12 started
11 termination
12 termination
created thread with id 14
created thread with id 15
Uncaught exception in 11
java.lang.RuntimeException
    at smp.Test.lambda$main$2(Test.java:28)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
15 started
14 started
Uncaught exception in 12
java.lang.RuntimeException
    at smp.Test.lambda$main$2(Test.java:28)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
14 termination
15 termination
Uncaught exception in 14
java.lang.RuntimeException
    at smp.Test.lambda$main$2(Test.java:28)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Uncaught exception in 15
java.lang.RuntimeException
    at smp.Test.lambda$main$2(Test.java:28)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Of course, the numbers and the order of the messages may differ.

As stated in the cite, you can also learn about exceptions by overriding afterExecute:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 1,
    TimeUnit.MINUTES, new LinkedBlockingQueue<>()) {
        @Override protected void afterExecute(Runnable r, Throwable t) {
            if(t!=null) synchronized(System.out) {
               System.out.println("Uncaught exception in "+Thread.currentThread().getId());
               t.printStackTrace(System.out);
            }
        }
    };

Though, in my tests, the default uncaught exception handler also printed stack traces…

Upvotes: 2

Related Questions