线程池执行过程中遇到异常该怎么办?

线程遇到未处理的异常就结束了

这个好理解,当线程出现未捕获异常的时候就执行不下去了,留给它的就是垃圾回收了。

线程池中线程频繁出现未捕获异常

当线程池中线程频繁出现未捕获的异常,那线程的复用率就大大降低了,需要不断地创建新线程。

做个实验:

java 复制代码
public class ThreadExecutor {
    private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS,
                                                                           new ArrayBlockingQueue<>(200), new ThreadFactoryBuilder().setNameFormat("customThread %d").build());
    @Test
    public void test() {
        IntStream.rangeClosed(1, 5).forEach(i -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            threadPoolExecutor.execute(() -> {
                int j = 1/0;
            });});
    }
}

新建一个只有一个线程的线程池,每隔0.1s提交一个任务,任务中是一个1/0的计算。

php 复制代码
Exception in thread "customThread 0" java.lang.ArithmeticException: / by zero
 at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 at java.lang.Thread.run(Thread.java:748)
Exception in thread "customThread 1" java.lang.ArithmeticException: / by zero
 at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 at java.lang.Thread.run(Thread.java:748)
Exception in thread "customThread 2" java.lang.ArithmeticException: / by zero
 at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 at java.lang.Thread.run(Thread.java:748)
Exception in thread "customThread 3" java.lang.ArithmeticException: / by zero
 at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 at java.lang.Thread.run(Thread.java:748)
Exception in thread "customThread 4" java.lang.ArithmeticException: / by zero
 at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 at java.lang.Thread.run(Thread.java:748)
Exception in thread "customThread 5" java.lang.ArithmeticException: / by zero
 at thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 at java.lang.Thread.run(Thread.java:748)

可见每次执行的线程都不一样,之前的线程都没有复用。原因是因为出现了未捕获的异常。

把异常捕获试试:

csharp 复制代码
public class ThreadExecutor {
    private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS,
                                                                           new ArrayBlockingQueue<>(200), new ThreadFactoryBuilder().setNameFormat("customThread %d").build());
    @Test
    public void test() {
        IntStream.rangeClosed(1, 5).forEach(i -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            threadPoolExecutor.execute(() -> {
                try {
                    int j = 1 / 0;
                } catch (Exception e) {
                    System.out.println(Thread.currentThread().getName() +" "+ e.getMessage());
                }
            });
        });
    }
}
csharp 复制代码
customThread 0 / by zero
customThread 0 / by zero
customThread 0 / by zero
customThread 0 / by zero
customThread 0 / by zero

可见当异常捕获了,线程就可以复用了。

问题来了,代码中异常不可能全部捕获

如果要捕获那些没被业务代码捕获的异常,可以设置Thread类的uncaughtExceptionHandler属性。这时使用ThreadFactoryBuilder会比较方便,ThreadFactoryBuilder是guava提供的ThreadFactory生成器。

scss 复制代码
new ThreadFactoryBuilder()
    .setNameFormat("customThread %d")
    .setUncaughtExceptionHandler((t, e) -> System.out.println(t.getName() + "发生异常" + e.getCause()))
    .build()

修改之后:

csharp 复制代码
public class ThreadExecutor {
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS,
                                                                                  new ArrayBlockingQueue<>(200),
                                                                                  new ThreadFactoryBuilder()
                                                                                  .setNameFormat("customThread %d")
                                                                                  .setUncaughtExceptionHandler((t, e) -> System.out.println("UncaughtExceptionHandler捕获到:" + t.getName() + "发生异常" + e.getMessage()))
                                                                                  .build());
    @Test
    public void test() {
        IntStream.rangeClosed(1, 5).forEach(i -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            threadPoolExecutor.execute(() -> {
                System.out.println("线程" + Thread.currentThread().getName() + "执行");
                int j = 1 / 0;
            });
        });
    }
}
csharp 复制代码
线程customThread 0执行
UncaughtExceptionHandler捕获到:customThread 0发生异常/ by zero
线程customThread 1执行
UncaughtExceptionHandler捕获到:customThread 1发生异常/ by zero
线程customThread 2执行
UncaughtExceptionHandler捕获到:customThread 2发生异常/ by zero
线程customThread 3执行
UncaughtExceptionHandler捕获到:customThread 3发生异常/ by zero
线程customThread 4执行
UncaughtExceptionHandler捕获到:customThread 4发生异常/ by zero

可见,结果并不是想象的那样,线程池中原有的线程没有复用!所以通过UncaughtExceptionHandler想将异常吞掉使线程复用这招貌似行不通。它只是做了一层异常的保底处理。

excute改成submit试试

csharp 复制代码
public class ThreadExecutor {
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS,
                                                                                  new ArrayBlockingQueue<>(200),
                                                                                  new ThreadFactoryBuilder()
                                                                                  .setNameFormat("customThread %d")
                                                                                  .setUncaughtExceptionHandler((t, e) -> System.out.println("UncaughtExceptionHandler捕获到:" + t.getName() + "发生异常" + e.getMessage()))
                                                                                  .build());
    @Test
    public void test() {
        IntStream.rangeClosed(1, 5).forEach(i -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Future<?> future = threadPoolExecutor.submit(() -> {
                System.out.println("线程" + Thread.currentThread().getName() + "执行");
                int j = 1 / 0;
            });
            try {
                future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}
csharp 复制代码
线程customThread 0执行
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
线程customThread 0执行
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
线程customThread 0执行
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
线程customThread 0执行
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
线程customThread 0执行
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero

通过submit提交线程可以屏蔽线程中产生的异常,达到线程复用。当get()执行结果时异常才会抛出。

原因是通过submit提交的线程,当发生异常时,会将异常保存,待future.get();时才会抛出。

这是Futuretask的部分run()方法,看setException

ini 复制代码
public void run() {
    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);
            }
            if (ran)
                set(result);
        }
    } 
}
protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

将异常存在outcome对象中,没有抛出,再看get方法:

ini 复制代码
public V get() 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);
}

当outcome是异常时才抛出。

总结

1、线程池中线程中异常尽量手动捕获

2、通过设置ThreadFactoryUncaughtExceptionHandler可以对未捕获的异常做保底处理,通过execute提交任务,线程依然会中断,而通过submit提交任务,可以获取线程执行结果,线程异常会在get执行结果时抛出。

如果小假的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!

相关推荐
karry_k2 小时前
常用的同步辅助类
后端
稚辉君.MCA_P8_Java2 小时前
DeepSeek Java 单例模式详解
java·spring boot·微服务·单例模式·kubernetes
洛_尘2 小时前
数据结构--4:栈和队列
java·数据结构·算法
疯癫的老码农2 小时前
【小白入门docker】创建Spring Boot Hello World应用制作Docker镜像并运行
java·spring boot·分布式·docker·微服务
Mr.Entropy2 小时前
Hibernate批量查询方法全面解析
java·后端·hibernate
绝顶少年3 小时前
Spring 框架中 RestTemplate 的使用方法
java·后端·spring
小趴菜82273 小时前
安卓人机验证View
android·java·前端
信安成长日记3 小时前
golang 写路由的时候要注意
开发语言·后端·golang
Lojarro3 小时前
GO学习2:基本数据类型 与 转换
后端·学习·golang