Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Thread exception catch problem

Java exceptions are not shared between threads; exceptions thrown within a thread are their own and cannot be caught by the main thread. That is, you treat the code that the thread executes as another main function.

The operation of the above A and B are independent of each other, although you see in the content in the main function of the code block B, but the main do not catch exceptions to this function in the Runnable, because it is not in the same thread running, the exception thrown in the B if you are not in another thread capture, equivalent to is that there is no exception handling, Unable to capture.

In Java multithreaded programs, all threads are not allowed to throw uncaught Checked exceptions, that is, each thread needs to handle them by itself. The run method is restricted to throw exceptions (throws Exception).

Public class ThreadSample implements Runnable {@override public void run() {// Run () cannot be thrown System.out.println(" the task starts executing "); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } int i = 10 /0; // This throws RuntimeException() system.out.println (" task completed "); } } public class ThreadErrorTest { public static void main(String[] args) { Thread a = new Thread(new ThreadSample()); a.start(); System.out.println(" Main thread execution complete!! ") ); }}Copy the code
  • Thread A throws an exception without affecting the main thread. When such an exception occurs, the child thread terminates.

  • Therefore, there is no way to catch exceptions thrown by child threads in the main thread and handle them, only try/catch the business logic inside the run method.

  • Threads are pieces of code that execute independently, and thread problems should be solved by the thread itself, not by delegation.

Set exception catcher for threads

Thread in Java provides a method to set up the Thread of setUncaughtExceptionHandler exception handler, you can hand in, and the exception handler when a Thread is not an exception, back and forth by the JVM tuning is carried out.

How do I get thread error execution results within child threads?

  • Can use Thread. UncaughtExceptionHandler set exception handler for each Thread, Thread. UncaughtExceptionHandler. UncaughtException () method in a Thread when facing death due to an uncaught exception is called, The above child thread itself is also printed to the console due to UncaughtExceptionHandler because of abnormal termination

  • [UncaughtExceptionHandler] [UncaughtExceptionHandler] [uncaughtException]

public class ThreadErrorTest {

    public static void main(String[] args) {
        Thread a = new Thread(new ThreadSample());
        a.setUncaughtExceptionHandler(new RuntimeExceptionHandle());
        a.start();
        System.out.println("Main thread execution completed!!"); }}public class RuntimeExceptionHandle implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        // Prints abnormal information to logs
        System.out.println("Exception handler call, print log:"+ e); }}Copy the code

The child thread throws a runtime exception and calls a custom exception handler for exception handling (logging)

The principle of analysis

  1. When a Thread is terminated due to an uncaught exception, the JAVA virtual machine will use the Thread, getUncaughtExceptionHandler () query this Thread to get its UncaughtExceptionHandler

  2. The handler’s uncaughtException() method is called, passing the thread and exception as arguments.

  3. If not, the exception handler for the ThreadGroup of that thread is searched.

  4. The default exception handler implementation in a ThreadGroup delegates processing layer by layer to the top ThreadGroup until a ThreadGroup’s exception handler is able to handle the exception, and then to the top ThreadGroup.

  5. The exception handler of the top-level ThreadGroup delegates to the default System handler (null if the default handler exists), otherwise the stack information is printed to System.err

Thread pool exception catching?

There are also several ways for the thread pool to handle uncatched exceptions:
  1. All the try/catch logic in the run method
public void run(a) {
   try {
    // Process logic
   } catch(Exeception e) {
      // Prints logs}}Copy the code

This is a simple and error-proof way to handle thread pool exceptions and is recommended.

  1. Rewrite the ThreadPoolExecutor afterExecute method

Any thread in the thread pool must call the afterExecute method before execution ends, so you just need to override it.

public class MyThreadPool extends ThreadPoolExecutor {
   
    public MyThreadPool(int corePoolSize, int maximumPoolSize,
                        long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    public void afterExecute(Runnable r, Throwable t) {
        if(t ! =null) {
            System.out.println("Print exception log:"+ t); }}}Copy the code
  • Analysis thread pool source code:
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null.false);
    }
    else if(! addWorker(command,false))
        reject(command);
}
/** * addWorker */
w = new Worker(firstTask); // Encapsulate the Worker object
final Thread t = w.thread;
if(t ! =null) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // Recheck while holding lock.
        // Back out on ThreadFactory failure or if
        // shut down before lock acquired.
        int rs = runStateOf(ctl.get());
        if (rs < SHUTDOWN ||
            (rs == SHUTDOWN && firstTask == null)) {
            if (t.isAlive()) // precheck that t is startable
                throw new IllegalThreadStateException();
            workers.add(w);
            int s = workers.size();
            if (s > largestPoolSize)
                largestPoolSize = s;
            workerAdded = true; }}finally {
        mainLock.unlock();
    }
    if (workerAdded) {
        t.start();
        workerStarted = true;
    }
/** * Part of the run method in the worker object */
while(task ! =null|| (task = getTask()) ! =null) {
    w.lock();
     if((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && ! wt.isInterrupted()) wt.interrupt();try {
         beforeExecute(wt, task);
         Throwable thrown = null;
         try {
             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); // execute the change method}}finally {
         task = null; w.completedTasks++; w.unlock(); }}Copy the code
  • The execute method in ThreadPoolExecutor encapsulates the incoming task as a Worker object. When the run method enters the Worker object, the exception is caught by the thread pool.

  • But finally the afterExecute(Task, Thrown) method is executed in finally, and the method body is empty with no logic in it.

  1. Use Submit to perform the task

We know that when we use Submit to execute a task, the method will return a Future object. Not only will the result of the task be executed, but the exception will also be encapsulated in the Future object, which is obtained by the get() method.

publicFuture<? > submit(Runnable task) {if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask); // Encapsulate it as a FutureTask object and pass it to the execute method
        return ftask;
}
Copy the code

Since calling ThreadPoolExecutor’s execute method is encapsulated as a Worker object, FutureTask’s run method is then called:

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 {
	             result = c.call();
	             ran = true;
	         } catch (Throwable ex) {
	             result = null;
	             ran = false;
	             setException(ex);  // Catch an exception
	         }
	         if(ran) set(result); }}finally{... }Copy the code

After catching an exception, setException(ex) is called. SetExcetion first assigns an exception information to a global variable outcome, and updates the global task state field to 3(exception state) through CAS, and finally does some cleaning.

The futureTask.get () method makes some logical judgments about the outcome and state set in the setException method and throws an exception directly up, so we can catch the exception in the main thread.

public V get(a) throws InterruptedException, ExecutionException {
   int s = state;
     if (s <= COMPLETING)
         s = awaitDone(false.0L);
     return report(s);
 }

private V report(int s) throws ExecutionException {
   Object x = outcome;
     if (s == NORMAL)
         return (V)x;
     if (s >= CANCELLED)
         throw new CancellationException();
     throw new ExecutionException((Throwable)x);
 }
Copy the code

Custom exception handlers

Since the Thread pool takes a Runnable argument, not a Thread, to perform each task, we need to create a Thread pool using ThreadFactory

public class MyThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setUncaughtExceptionHandler(new RuntimeExceptionHandle());
        returnt; }}Copy the code

Inherit ThreadFactory and rewrite the newThread(Runnable R) method to set up the exception handler where exceptions are caught and handled (print the log)

public class ThreadPoolExecption1 {
    private static ExecutorService executor = Executors.newSingleThreadExecutor(new MyThreadFactory());

    public static void main(String[] args) {
        Task task = newTask(); executor.execute(task); executor.submit(task); }}Copy the code

It is easier to set up a static field in the Thread class and set this handler as the default exception handler. However, this is global, so use with caution. If you need customization, you need to customize it.

public class ThreadPoolException2 {

    private static ExecutorService executor = Executors.newFixedThreadPool(3);

    public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler(new RuntimeExceptionHandle());
        Task task = new Task();
        executor.execute(task);
        //executor.submit(task);}}Copy the code