多线程中如何进行异常处理?

一、Thread的默认异常处理

线程不允许抛出未捕获的checked exception(比如sleep时的InterruptedException),也就是说各个线程需要自己把自己的checked exception处理掉。我们可以查看一下Thread类的run()方法声明,方法声明上没有对抛出异常进行任何约束。

    //Thread类中
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
    //Runnable接口中
    public abstract void run();

 

 

 

 

 

 

JVM的这种设计源自于这样一种理念:“线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。”基于这样的设计理念,在Java中,线程方法的异常(无论是checked exception还是unchecked exception),都应该在线程代码边界之内(run方法内)进行try catch并处理掉。换句话说,我们不能捕获从线程中逃逸的异常。

二、未捕获的异常如何处理的

一个异常被抛出后,如果没有被捕获处理,则会一直向上抛。异常一旦被Thread.run() 抛出后,就不能在程序中对异常进行捕获,最终只能由JVM捕获。

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.currentThread().getName());

        //发生异常
        int i = 1 / 0;

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

 

 

 

接下来我们尝试对异常进行捕获,看看是否能捕获到

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("捕获线程抛出的异常!" + e.getMessage());
        }

    }
}

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

        //发生异常
        int i = 1 / 0;

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

 

 

 

结果我们发现,我们尝试在main方法中对线程中抛出的异常进行捕获,但是毫无作用。

三、那么,JVM如何处理线程中抛出的异常的呢

查看Thread类的源码,我们可以看到有个dispatchUncaughtException方法,此方法就是用来处理线程中抛出的异常的。JVM会调用dispatchUncaughtException方法来寻找异常处理器(UncaughtExceptionHandler),处理异常。

    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
     //向handler分派未捕获的异常。该方法仅由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
     */
    // 获取用来处理未捕获异常的handler,如果没有设置则返回当前线程所属的ThreadGroup
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

 

 

 

UncaughtExceptionHandler必须显示的设置,否则默认为null。若为null,则使用线程默认的handler,即该线程所属的ThreadGroup。ThreadGroup自身就是一个handler,查看ThreadGroup的源码就可以发现,ThreadGroup实现了Thread.UncaughtExceptionHandler接口,并实现了默认的处理方法。默认的未捕获异常处理器处理时,会调用 System.err 进行输出,也就是直接打印到控制台了。

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);
            }
        }
    }

 

 

 

由此可知,最终JVM是调用未捕获的异常处理器的uncaughtException()方法来处理异常的,并且是直接打印到控制台。

四、如何自定义处理线程异常

如果我们要自己处理异常,该怎么办呢?通过前面的分析,我们已经知道了线程会使用默认的未捕获异常处理器来处理异常。自然我们可以想到,是否可以自定义未捕获异常处理器,覆盖掉默认的捕获异常处理器。实际上,Thead确实已经提供了一个setUncaughtExceptionHandler方法,我们只需要将自定义未捕获异常处理器作为参数传入进入就可以了。

 

 

 

接下来我们自定义处理线程异常

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.currentThread().getName());

        //发生异常
        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("捕获线程抛出的异常:" + e.getMessage());
            }
        });
    }
}

 

 

 

五、线程池中自定义处理异常

要自定义处理异常,只需要为线程提供一个自定义的UncaughtExceptionHandler。而在线程池中,该如何批量的为所有线程设置UncaughtExceptionHandler呢?我们知道,线程池中的线程是由线程工厂创建的。我们可以跟踪ThreadPoolExecutor构造方法的源码,最终定位到DefaultThreadFactory类,该类中有个newThread()方法,这就是线程产生的源头了。

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

//DefaultThreadFactory类中
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;
        }

 

 

 

 

 

 

 

 

 

找到了线程创建的源头,我们就可以实现一个自己的线程工厂,并覆盖默认的newThread方法,为新创建的线程提供一个UncaughtExceptionHandler,即可达到我们的目的。由于DefaultThreadFactory是Executors类的内部类,我们不能直接继承该类,只能实现该工厂类的接口——ThreadFactory接口,来实现我们自己的线程工厂。

package com.joshua317;

import java.util.concurrent.ThreadFactory;

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

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("捕获线程抛出的异常:" + e.getMessage());
    }
}

 

posted @ 2022-01-29 20:27  joshua317  阅读(581)  评论(0编辑  收藏  举报