ByteD2: A thread in the thread pool throws an exception, how to deal with it?

20b8f852259 53306c38b3e7742ac0d90.jpeg

The path to growth as a programmer

Internet/Programmer/Technology/Data Sharing

focus on

This article should take approximately 12 minutes to read.

From: Internet, intrusion and deletion

In actual development, we often use thread pools, but once a task is submitted to the thread pool, what should we do if an exception occurs? How to obtain exception information? Before understanding this problem, you can first take a look at the source code analysis of the thread pool. From the source code, we know the submission method of the thread pool: the difference between submit and execute. Next, use them to execute tasks with exceptions! See what the result is!

Let’s first use pseudo code to simulate the scenario where the thread pool throws an exception:

public class ThreadPoolException {
    public static void main(String[] args) {

        //Create a thread pool
        ExecutorService executorService= Executors.newFixedThreadPool(1);

        //When the thread pool throws an exception, submit is silent and other threads continue to execute.
        executorService.submit(new task());

        //When the thread pool throws an exception, execute throws an exception and other threads continue to execute new tasks.
        executorService.execute(new task());
    }
}

//task class
class task implements Runnable{

    @Override
    public void run() {
        System.out.println("Entered task method!!!");
        int i=1/0;

    }
}

operation result:

37bcd30924710e4643db8940a2f2b4ff.png

picture

You can see: submit does not print exception information, but execute does! , the submit method does not print exception information, which is obviously not feasible in production, because we cannot guarantee that the tasks in the thread will never be abnormal, and if an exception occurs using the submit method, we will not be able to obtain it by writing it directly as above Exception information, make corresponding judgment and processing, so the next step is to know how to get the exception thrown by the thread pool!

submit() If you want to obtain exception information, you must use the get() method! !

//When the thread pool throws an exception, submit is silent and other threads continue to execute.
Future<?> submit = executorService.submit(new task());
submit.get();

Submit prints the exception information as follows:

3e9d4444f8a7a5fd35b7ea17f60363c6.png

picture

Option 1: Use try -catch

public class ThreadPoolException {
    public static void main(String[] args) {
        
        //Create a thread pool
        ExecutorService executorService = Executors.newFixedThreadPool(1);

        //When the thread pool throws an exception, submit is silent and other threads continue to execute.
        executorService.submit(new task());

        //When the thread pool throws an exception, execute throws an exception and other threads continue to execute new tasks.
        executorService.execute(new task());
    }
}
//Task class
class task implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("Entered task method!!!");
            int i = 1 / 0;
        } catch (Exception e) {
            System.out.println("Use try -catch to catch exception" + e);
        }
    }
}

Print the result:

8104b0b694570d0692396e988ff9b7a0.png

picture

You can see that both submit and execute have captured the exception clearly and understandably, and you can know that there is a problem with our task instead of disappearing without a trace.

Option 2: Use the Thread.setDefaultUncaughtExceptionHandler method to catch exceptions

In the first option, adding a try-catch to each task is really troublesome, and the code is not good-looking. If you think about it this way, you can use Thread.setDefaultUncaughtExceptionHandler Method catching exception

8c29fb6b17bebd4e4ecd1c9eb274409f.png

picture

UncaughtExceptionHandler is an internal class of Thread class and a functional interface.

The internal uncaughtException is a method for handling exceptions that occur within a thread. The parameters are the thread object t and the exception object e.

8fea56e4a407a46b2140c3449e710b30.png

picture

The application in the thread pool is as follows: rewrite its thread factory method, and assign the UncaughtExceptionHandler handler object when the thread factory creates a thread.

public class ThreadPoolException {
    public static void main(String[] args) throws InterruptedException {


        //1. Implement your own thread pool factory
        ThreadFactory factory = (Runnable r) -> {
            //Create a thread
            Thread t = new Thread(r);
            //Set the UncaughtExceptionHandler object for the created thread to implement the default logic of the exception.
            t.setDefaultUncaughtExceptionHandler((Thread thread1, Throwable e) -> {
                System.out.println("exceptionHandler set by the thread factory" + e.getMessage());
            });
            return t;
        };

        //2. Create a self-defined thread pool and use a self-defined thread factory
        ExecutorService executorService = new ThreadPoolExecutor(
                1,
                1,
                0,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue(10),
                factory);

        //submit without prompt
        executorService.submit(new task());

        Thread.sleep(1000);
        System.out.println("================== To verify the print results, execute the execute method after 1 second");

        // The execute method catches the exception by the UncaughtExceptionHandler of the thread factory.
        executorService.execute(new task());


    }


}

class task implements Runnable {
    @Override
    public void run() {
        System.out.println("Entered task method!!!");
        int i = 1 / 0;
    }
}

The print result is as follows:

![Picture](data:image/svg + xml,)

According to the printing results, we can see that the execute method caught the exception by the UncaughtExceptionHandler set in the thread factory, but the submit method did not respond! Description: UncaughtExceptionHandler is not called in submit. Why is this?

In daily use, we know that the biggest difference between execute and submit is that execute has no return value, while submit has a return value. Submit returns a future, through which the thread execution results or exception information can be obtained.

Future<?> submit = executorService.submit(new task());
//Print abnormal results
  System.out.println(submit.get());

![Picture](data:image/svg + xml,)

It can be seen from the results: Submit does not lose the exception, and there is still an exception printed when using future.get()! ! So why does the thread factory’s UncaughtExceptionHandler not print exceptions? The guess is that the exception has been caught inside the submit method, but it has not been printed out. Because the exception has been caught, the jvm will not call Thread’s UncaughtExceptionHandler to handle the exception.

Next, verify the conjecture:

First, take a look at the source code of submit and execute:

The source code of the execute method is written in detail in this blog. Click to view the execute source code. I won’t go into details here.

https://blog.csdn.net/qq_45076180/article/details/108316340

The submit source code still calls the execute method at the bottom, but it has an extra layer of Future encapsulation and returns this Future. This also explains why submit has a return value.

//submit() method
 public <T> Future<T> submit(Callable<T> task) {
     if (task == null) throw new NullPointerException();
     
     //execute internally executes the internal logic of this object, and then sets the result or exception into this ftask.
     RunnableFuture<T> ftask = newTaskFor(task);
     //Execute the execute method
     execute(ftask);
     //Return this ftask
     return ftask;
 }

You can see that submit is also called execute. In the execute method, our task is submitted to addWorker(command, true), and then a Worker is created for each task to process the thread. This Worker is also A thread calls the Worker’s run method when executing a task! The runworker method is called inside the run method! As follows:

public void run() {
        runWorker(this);
 }
     
final void runWorker(Worker w) {
     Thread wt = Thread.currentThread();
     Runnable task = w.firstTask;
     w.firstTask = null;
     w.unlock(); // allow interrupts
     boolean completedAbruptly = true;
     try {
      //Here is the reason why threads can be reused, loop + conditional judgment, constantly fetching tasks from the queue
      //Another question is how to solve the timeout deletion of non-core threads
      //Mainly the getTask method() see below ③
         while (task != null || (task = getTask()) != null) {
             w.lock();
             if ((runStateAtLeast(ctl.get(), STOP) ||
                  (Thread.interrupted() & amp; & amp;
                   runStateAtLeast(ctl.get(), STOP))) & amp; & amp;
                 !wt.isInterrupted())
                 wt.interrupt();
             try {
                 beforeExecute(wt, task);
                 Throwable thrown = null;
                 try {
                  //Execution thread
                     task.run();
                     //Exception handling
                 } catch (RuntimeException x) {
                     thrown = x; throw x;
                 } catch (Error x) {
                     thrown = x; throw x;
                 } catch (Throwable x) {
                     thrown = x; throw new Error(x);
                 } finally {
                  //Execute method can override this method to handle exceptions
                     afterExecute(task, thrown);
                 }
             } finally {
                 task = null;
                 w.completedTasks + + ;
                 w.unlock();
             }
         }
         //completeAbruptly will not be modified to false when an exception occurs
         completedAbruptly = false;
     } finally {
      //If the completedAbruptly value is true and an exception occurs, add a new Worker to process the subsequent thread.
         processWorkerExit(w, completedAbruptly);
     }
 }

The core is in the task.run(); method. If an exception occurs during this period, it will be thrown.

  • If a task is submitted using execute, it will be encapsulated into a runable task, and then encapsulated into a worker. Finally, the runWoker method is called in the worker’s run method, and the task is executed in the runWoker method. If an exception occurs in the task, use try-catch to catch the exception and throw it outside. We use try-catch to capture the exception in the runWoker method in the outermost layer. Exception thrown. So we saw the exception information of our task in execute.

  • So why does submit have no exception information? Because submit encapsulates the task into a futureTask, and then this futureTask is encapsulated into a worker. In the woker’s run method, futureTask is ultimately called. code>’s run method, I guess it swallowed the exception directly and did not throw an exception, so the exception cannot be caught in the worker’s runWorker method.

Let’s take a look at the run method of futureTask. Sure enough, the exception is swallowed in try-catch and placed in setException(ex);

public void run() {
     if (state != NEW ||
         !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                      null, Thread.currentThread()))
         return;
     try {
         Callable<V> c = callable;
         if (c != null & amp; & amp; state == NEW) {
             V result;
             boolean ran;
             try {
                 result = c.call();
                 ran = true;
             } catch (Throwable ex) {
                 result = null;
                 ran = false;
                 //Exception information is set in this method
                 setException(ex);
             }
             if (ran)
                 set(result);
         }
         //Omit the following
 . . . . . .
The setException(ex)` method is as follows: assign the exception object to `outcome
protected void setException(Throwable t) {
       if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        //Assign the exception object to outcome and remember this outcome.
           outcome = t;
           UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
           finishCompletion();
       }
   }

What is the use of assigning an exception object to outcome? What is this outcome? When we use submit to return a Future object and use Future.get(), the internal report method will be called!

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    //Pay attention to this method
    return report(s);
}

The report actually returns outcome, and the previous exception was set into this outcome.

private V report(int s) throws ExecutionException {
 //Set `outcome`
    Object x = outcome;
    if (s == NORMAL)
     //Return `outcome`
        return (V)x;
    if (s >= CANCELLED)
        throw new CancellationException();
    throw new ExecutionException((Throwable)x);
}

Therefore, when submitting with submit, the runable object is encapsulated into a future. When the run method in the future handles exceptions, it try-catch catches all exceptions through setException(ex) ;The method is set to the variable outcome, and the outcome can be obtained through future.get.

Therefore, when submitting, if an exception occurs, no information will be thrown. And through future.get() you can get the exception thrown by submit! In submit, there is no other method except getting the exception from the return result. Therefore, when there is no need to return a result, it is best to use execute, so that even if try-catch is not written and exception catching is omitted, the exception information will not be lost.

Option 3: Rewrite afterExecute for exception handling

Through the above source code analysis, in the exclude method, exception handling can be performed by rewriting afterExecute, but pay attention! This only applies to excute submission (the submit method is more troublesome, as discussed below), because the exception is swallowed in submit’s task.run, and no exception will come out at all, so there will be no exception. Enter afterExecute.

In runWorker, after calling task.run, the thread pool’s afterExecute(task, thrown) method will be called.

final void runWorker(Worker w) {
//Current thread
        Thread wt = Thread.currentThread();
        //Our submitted task
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() & amp; & amp;
                      runStateAtLeast(ctl.get(), STOP))) & amp; & amp;
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                    //Directly call the run method of the task
                        task.run(); //If it is the run of futuretask, the exception is swallowed and no exception will be thrown.
                       // Therefore Throwable thrown = null; will not enter the catch
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                    //Call the afterExecute method of the thread pool and pass in the task and exception
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks + + ;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

Rewrite afterExecute to handle exceptions submitted by execute

public class ThreadPoolException3 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {


        //1. Create a self-defined thread pool
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                3,
                0,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue(10)
        ) {
            //Rewrite the afterExecute method
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                System.out.println("Get the exception information in afterExecute and handle the exception" + t.getMessage());
            }
        };
        
        //Execute when the thread pool throws an exception
        executorService.execute(new task());
    }
}

class task3 implements Runnable {
    @Override
    public void run() {
        System.out.println("Entered task method!!!");
        int i = 1 / 0;
    }
}

Execution results: We can handle exceptions inside the afterExecute method

![Picture](data:image/svg + xml,)

If you want to use this afterExecute to handle the exception submitted by submit, additional processing is required. Determine whether Throwable is FutureTask. If it is an exception submitted by submit, the code is as follows:

public class ThreadPoolException3 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {


        //1. Create a self-defined thread pool
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                3,
                0,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue(10)
        ) {
            //Rewrite the afterExecute method
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                //This is when excute is submitted
                if (t != null) {
                    System.out.println("Get the exception information submitted by excute in afterExecute and handle the exception" + t.getMessage());
                }
                //If the actual type of r is FutureTask, then it was submitted by submit, so you can get the exception in it.
                if (r instanceof FutureTask) {
                    try {
                        Future<?> future = (Future<?>) r;
                        //get gets exception
                        future.get();

                    } catch (Exception e) {
                        System.out.println("obtain the exception information submitted by submit in afterExecute and handle the exception" + e);
                    }
                }
            }
        };
        //Execute when the thread pool throws an exception
        executorService.execute(new task());
        
        //When the thread pool throws an exception, submit
        executorService.submit(new task());
    }
}

class task3 implements Runnable {
    @Override
    public void run() {
        System.out.println("Entered task method!!!");
        int i = 1 / 0;
    }
}

The processing results are as follows:

d725945bdbf37b065e60d4c047c4c880.png

You can see that by rewriting afterExecute, you can handle exceptions thrown by execute as well as exceptions thrown by submit.

Recommended reading:

The blog Lei Jun wrote when he was a programmer is awesome!

Disadvantages of UUID and Snowflake Algorithm

Internet interview questions for junior high school and senior companies (9 Gs)
The content includes Java basics, JavaWeb, MySQL performance optimization, JVM, locks, millions of concurrency, message queues, high-performance caching, reflection, Spring family bucket principles, microservices, Zookeeper... and other technology stacks!
?Click to read the original text to get it! I have read it