How to handle exceptions in multithreading?

Posted by kenwvs on Mon, 31 Jan 2022 12:40:08 +0100

This article is an original article of Joshua 317. Please indicate: reprinted from Joshua 317 blog https://www.joshua317.com/article/240

1, Default exception handling for Thread

Threads are not allowed to throw uncapped checked exceptions (such as InterruptedException in sleep), that is, each Thread needs to dispose of its own checked exceptions. We can take a look at the run() method declaration of the Thread class. There are no constraints on throwing exceptions on the method declaration.

    //Thread class
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
    //In Runnable interface
    public abstract void run();

 

 

 

 

 

 

This design of JVM comes from the idea that "thread is a code fragment executed independently, and the problem of thread should be solved by the thread itself rather than entrusted to the outside." Based on this design concept, in Java, the exceptions of thread methods (whether checked exception or unchecked exception) should be try caught and handled within the thread code boundary (within the run method). In other words, we cannot catch exceptions that escape from the thread.

2, How are uncapped exceptions handled

After an exception is thrown, if it is not caught and handled, it will be thrown up all the time. Once the exception is thread After run() is thrown, the exception cannot be caught in the program, but can only be caught by the JVM in the end.

package com.joshua317;

public class ThreadException {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run()
    {
        System.out.println("Thread Name:" + Thread.currentThread().getName());

        //exception occurred
        int i = 1 / 0;

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

 

 

Next, we try to catch the exception to see if we can catch it

package com.joshua317;

public class ThreadException {
    public static void main(String[] args) {
        try {
            MyThread myThread = new MyThread();
            myThread.start();
        }catch (Exception e) {
            System.out.println("Catch the exception thrown by the thread!" + e.getMessage());
        }

    }
}

class MyThread extends Thread {
    @Override
    public void run()
    {
        System.out.println("Thread Name:" + Thread.currentThread().getName());

        //exception occurred
        int i = 1 / 0;

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

 

 

As a result, we found that we tried to catch the exception thrown in the thread in the main method, but it didn't work.

3, So how does the JVM handle exceptions thrown in threads

Looking at the source code of Thread class, we can see that there is a dispatchUncaughtException method, which is used to handle the exceptions thrown in the Thread. The JVM will call the dispatchUncaughtException method to find the exception handler and handle the exception.

    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
     //Dispatch unhandled exceptions to the handler. This method is only called by the JVM.
    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
    /**
     * Returns the handler invoked when this thread abruptly terminates
     * due to an uncaught exception. If this thread has not had an
     * uncaught exception handler explicitly set then this thread's
     * <tt>ThreadGroup</tt> object is returned, unless this thread
     * has terminated, in which case <tt>null</tt> is returned.
     * @since 1.5
     * @return the uncaught exception handler for this thread
     */
    // Gets the handler used to handle uncapped exceptions. If it is not set, it returns the ThreadGroup to which the current thread belongs
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

 

 

 

UncaughtExceptionHandler must display the settings of, otherwise it defaults to null. If it is null, the default handler of the thread is used, that is, the ThreadGroup to which the thread belongs. ThreadGroup itself is a handler. Looking at the source code of ThreadGroup, we can find that ThreadGroup implements thread UncaughtExceptionHandler interface and implements the default processing method. The default uncaught exception handler will call system Err for output, that is, print directly to the console.

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 (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

 

 

 

It can be seen from this that the final JVM calls the uncaughtException() method of the uncaught exception handler to handle the exception and prints it directly to the console.

4, How to customize thread exception handling

What should we do if we want to handle exceptions ourselves? Through the previous analysis, we have known that threads will use the default uncapped exception handler to handle exceptions. Naturally, we can think about whether we can customize the uncapped exception handler and override the default caught exception handler. In fact, Thead has indeed provided a setUncaughtExceptionHandler method. We only need to pass in the custom uncaught exception handler as a parameter.

 

 

 

Next, we customize the thread exception handling

package com.joshua317;

public class ThreadException {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setUncaughtExceptionHandler();
        myThread.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run()
    {
        System.out.println("Thread Name:" + Thread.currentThread().getName());

        //exception occurred
        int i = 1 / 0;

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void setUncaughtExceptionHandler()
    {
        this.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("Catch exceptions thrown by threads:" + e.getMessage());
            }
        });
    }
}

 

 

 

5, Custom exception handling in thread pool

To customize exception handling, you only need to provide a custom UncaughtExceptionHandler for the thread. In the thread pool, how to batch set UncaughtExceptionHandler for all threads? As we know, the threads in the thread pool are created by the thread factory. We can trace the source code of the ThreadPoolExecutor construction method and finally locate the DefaultThreadFactory class, which has a newThread() method, which is the source of threads.

//In ThreadPoolExecutor class
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    
//In the Executors class  
public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }
    

//In the DefaultThreadFactory class
public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }

 

 

 

 

 

 

 

 

 

After finding the source of thread creation, we can implement our own thread factory, override the default newThread method, and provide an UncaughtExceptionHandler for the newly created thread to achieve our goal. Since DefaultThreadFactory is an internal class of Executors class, we can't inherit this class directly. We can only implement the ThreadFactory interface, the interface of this factory class, to implement our own thread factory.

package com.joshua317;

import java.util.concurrent.ThreadFactory;

public class MyThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread();
        //Customize UncaughtExceptionHandler
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        return t;
    }
}

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("Catch exceptions thrown by threads:" + e.getMessage());
    }
}

 

This article is an original article of Joshua 317. Please indicate: reprinted from Joshua 317 blog https://www.joshua317.com/article/240

Topics: Java