preface

Author Mahua, public number [source interest circle], touched the piano under cooking, a program ape, not like a program ape, focus on the framework source code, concurrent programming, Python and other technologies

How to handle exceptions in the JDK thread pool

Working with a thread pool and not knowing how to handle exceptions or how to properly handle exceptions thrown

1. Read with questions

How does the thread pool print out exceptions thrown by running tasks?

2. Do thread pools execute() and submit() handle exceptions the same?

3. What are the ways to handle task exceptions?

According to the above problems, through the sample code and source code common parsing

Unless otherwise noted, use JDK 1.8 for reading

2. How do I handle exceptions thrown when a task is running

Execute () is an example of how this is handled in the source code

If you have read the previous two thread pool articles, the first task execution process is relatively clear

execute() -> addWorker() -> start() -> run() -> runWorker()

Put the ThreadPoolExecutor#runWorker source code directly here

final void runWorker(Worker w) {
.    try {
        while(task ! =null|| (task = getTask()) ! =null) {
            w.lock();
. try {  beforeExecute(wt, task);  Throwable thrown = null;  try {  / * ** Run the run method of the submitted task* If an exception is thrown, it will be caught by the following catch block* /  task.run();  } catch (RuntimeException x) {  thrown = x;  throw x;  } catch (Error x) {  thrown = x;  throw x;  } catch (Throwable x) {  thrown = x;  throw new Error(x);  } finally {  afterExecute(task, thrown);  }  } finally {  task = null;  w.completedTasks++;  w.unlock();  }  }  completedAbruptly = false;  } finally {  processWorkerExit(w, completedAbruptly);  } } Copy the code

When a task submitted to the thread pool executes an exception, it is caught by the catch block below and throws an exception to the JVM

Let’s look at the following test code. The logic is simple: create a thread pool and throw a runtime exception when submitting the first task

Don’t worry about the thread pool build parameters in the sample code

@Test
public void executorTest(a) {
    ThreadPoolExecutor pool =
            new ThreadPoolExecutor(1.3.1000, TimeUnit.HOURS, new LinkedBlockingQueue(5));
    pool.execute(() -> {
 int i = 1/0;  });   pool.shutdown(); } / * * * Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero  * at cn.hsa.tps.ThreadPoolExceptionTest.lambda$executorTest$0(ThreadPoolExceptionTest.java:22)  * at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)  * at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)  * at java.lang.Thread.run(Thread.java:748) * / Copy the code

Throwing an exception is understandable, but inside the thread pool we just throw an exception. How do we print the exception information?

With doubts, I searched the all-powerful “Baidu” and finally got the answer:

The exception thrown up will be invoked by the virtual machine JVM Thread# dispatchUncaughtException

/ * * * Dispatch an uncaught exception to the handler. This method is
 * intended to be called only by the JVM.
* /
private void dispatchUncaughtException(Throwable e) {
 getUncaughtExceptionHandler().uncaughtException(this, e); } Copy the code

We can also look at comments to get relevant information

To an uncaught exception handler distribution, dispatchUncaughtException method can only be the JVM is taken

The “program” that handles the uncaught exception is UncaughtExceptionHandler

Continue to check the relevant methods, Thread# getUncaughtExceptionHandler

public Thread.UncaughtExceptionHandler getUncaughtExceptionHandler(a) {
    returnuncaughtExceptionHandler ! =null ?
            uncaughtExceptionHandler : group;
}
Copy the code

This checks to see if the thread has an uncaptured processing policy, and if it does not, it executes using the policy of the default thread group

private ThreadGroup group;
public class ThreadGroup implements Thread.UncaughtExceptionHandler {... }Copy the code

Thread groups involve batch management threads, such as batch stop or suspend, which will not be discussed here

ThreadGroup#uncaughtException ThreadGroup#uncaughtException ThreadGroup#uncaughtException

public void uncaughtException(Thread t, Throwable e) {
    if(parent ! =null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh =
 Thread.getDefaultUncaughtExceptionHandler();  if(ueh ! =null) {  ueh.uncaughtException(t, e);  } else if(! (einstanceof ThreadDeath)) {  // This will eventually be called to print a stack of information  System.err.print("Exception in thread \""  + t.getName() + "\" ");  e.printStackTrace(System.err);  }  } } Copy the code

It’s basically just swallowing the exception, but it prints out the exception, and it tells us, where does the exception that was thrown from the thread pool end up

3. Execute () and submit() check whether exception processing is consistent

Submit () instead of execute()

@Test
public void executorTest(a) {
    ThreadPoolExecutor pool =
            new ThreadPoolExecutor(1.3.1000, TimeUnit.HOURS, new LinkedBlockingQueue(5));
    pool.submit(() -> {
 int i = 1/0;  });   pool.shutdown(); } Copy the code

Clear friends believe that when you see here, you will know that there will be no abnormal information printing

Why won’t it print? This problem follows the source code we look together

ThreadPoolExecutor#runWorker

You can see that the Task is no longer a related Runnable object, but a FutureTask

Moving on to see how the FutureTask source code executes

public void run(a) {
    if(state ! = NEW ||! UNSAFE.compareAndSwapObject(this, runnerOffset,
                    null, Thread.currentThread()))
        return;
 try {  Callable<V> c = callable;  if(c ! =null && state == NEW) {  V result;  boolean ran;  try {  // The run method is also called internally  result = c.call();  ran = true;  } catch (Throwable ex) {  result = null;  ran = false;  setException(ex);  }  if (ran)  set(result);  }  } finally { .} Copy the code

According to the source code, exceptions thrown by the execution process will be caught, so the exception information will not be printed by default

How do I know if submit() throws an exception?

The return value of submit() is the Future interface. The default implementation is FutureTask, and there is a get() method inside that can both get the return value and throw the corresponding exception

public V get(a) throws InterruptedException, ExecutionException {
    int s = state;
   // If a new or unfinished Future is blocked
    if (s <= COMPLETING)
        s = awaitDone(false.0L);
 / / 🚩 focus  return report(s); }  private V report(int s) throws ExecutionException {  Object x = outcome;  // The result is returned after normal completion  if (s == NORMAL)  return (V) x;  // The task was cancelled and an exception was thrown  if (s >= CANCELLED)  throw new CancellationException();  // This is an internal execution error  throw new ExecutionException((Throwable) x); } Copy the code

When a task is abnormal, an exception is reported. You can use the GET () method to return the task execution exception

4. What are the methods to handle task exceptions

1. Inside the task, try catch any step that might throw an exception

@Test
public void executorTest(a) {
    ThreadPoolExecutor pool =
            new ThreadPoolExecutor(1.3.1000, TimeUnit.HOURS, new LinkedBlockingQueue(5));
    pool.execute(() -> {
 try {  int i = 1/0;  } catch (Exception ex) { . }  });   pool.shutdown(); } Copy the code

Handle exceptions in a catch block, retry handling, or alarm exceptions

The same goes for the submit

2, override the thread UncaughtExceptionHandler

UncaughtExceptionHandler is used to handle the uncaught exception policy when creating a thread factory

@Test
public void executorTest(a) {
    Thread.UncaughtExceptionHandler sceneHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
 // Just for example, different scenarios are different  log.error(">>> Thread :: {}, exception handling... ", t.getName(), e);  }  };   ThreadFactory threadFactory = new ThreadFactoryBuilder()  .setUncaughtExceptionHandler(sceneHandler)  .setDaemon(true).setNameFormat("Named after thread pool function").build();   ThreadPoolExecutor pool =  new ThreadPoolExecutor(1.3.1000, TimeUnit.HOURS, new LinkedBlockingQueue(5), threadFactory);  pool.execute(() -> {  int i = 1 / 0;  });   pool.shutdown(); } Copy the code

The sample code is also relatively simple, customizes UncaughtExceptionHandler processing policy, specifies the custom processing policy when creating the thread factory, and assigns the thread factory to the thread pool

Override afterExecute method in thread pool

@Test
public void executorExceptionAfterExecuteTest(a) {
    ThreadPoolExecutor pool =
            new ThreadPoolExecutor(1.3.1000, TimeUnit.HOURS, new LinkedBlockingQueue(5)) {
                @Override
 public void afterExecute(Runnable r, Throwable t) {  // Just for example, different scenarios are different  log.error(">>> Exception handling... ", t);  }  }; .} Copy the code

4.1 Summary of Exception Handling

Due to the granularity and unfriendly processing of the second and third methods above, try catch in the first task can be directly used in daily work

Afterword.

Due to the limited level of the author, you are welcome to feedback and correct the article is not correct, thanks to 🙏

The likes of my friends are the biggest support for me. If I gain something from reading this article, I hope I can like it, comment on it and pay attention to it.

This article is formatted using MDNICE