Java线程池执行过程中遇到异常的原因和解决办法

 更新时间:2025年10月11日 09:38:52   作者:程序员小假  
当线程池中线程频繁出现未捕获的异常,那线程的复用率就大大降低了,需要不断地创建新线程,所以本文通过详细的代码给大家介绍了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的计算。

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)

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

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());
                }
            });
        });
    }
}
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生成器。

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

修改之后:

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;
            });
        });
    }
}
线程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试试

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();
            }
        });
    }
}
线程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

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方法:

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执行结果时抛出。

以上就是Java线程池执行过程中遇到异常的原因和解决办法的详细内容,更多关于Java线程池执行遇到异常的资料请关注脚本之家其它相关文章!

相关文章

  • Java swing实现应用程序对数据库的访问问题

    Java swing实现应用程序对数据库的访问问题

    这篇文章主要介绍了Java swing实现应用程序对数据库的访问,本次实验需要做一个GUI界面和一个连接查询功能,在论坛上借鉴了其他大佬获取网站内容的部分代码,然后自己做了一个及其简陋的swing界面,算是把这个实验完成了,需要的朋友可以参考下
    2022-09-09
  • MyBatis深入解读懒加载的实现

    MyBatis深入解读懒加载的实现

    顾名思义,懒加载就是因为偷懒了,懒得加载了,只有使用的时候才进行加载。其实,懒加载也加延迟加载,主要以应用与Mybatis的关联查询,按照设置的延迟规则,推迟对延迟对关联对象的select查询
    2022-04-04
  • Java中的读写锁ReentrantReadWriteLock源码分析

    Java中的读写锁ReentrantReadWriteLock源码分析

    这篇文章主要介绍了Java中的读写锁ReentrantReadWriteLock源码分析,ReentrantReadWriteLock 分为读锁和写锁两个实例,读锁是共享锁,可被多个线程同时使用,写锁是独占锁,持有写锁的线程可以继续获取读锁,反之不行,需要的朋友可以参考下
    2023-12-12
  • 详解java.lang.NumberFormatException错误及解决办法

    详解java.lang.NumberFormatException错误及解决办法

    这篇文章主要介绍了详解java.lang.NumberFormatException错误及解决办法,本文详解的介绍了错误的解决方法,感兴趣的可以一起来了解一下
    2020-05-05
  • Java面试Logback打印日志如何获取当前方法名称题解

    Java面试Logback打印日志如何获取当前方法名称题解

    这篇文章主要为大家介绍了Java面试Logback打印日志如何获取当前方法名称题解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • Java 连接Access数据库的两种方式

    Java 连接Access数据库的两种方式

    这篇文章主要介绍了Java 连接Access数据库的两种方式,本文着重讲解使用JDBC连接操作Access数据库,需要的朋友可以参考下
    2015-06-06
  • java简单实现用语音读txt文档方法总结

    java简单实现用语音读txt文档方法总结

    在本篇文章里小编给大家整理了关于java简单实现用语音读txt文档的详细方法总结,有需要的朋友们参考下。
    2019-06-06
  • Java中数据库常用的两把锁之乐观锁和悲观锁

    Java中数据库常用的两把锁之乐观锁和悲观锁

    这篇文章主要介绍了数据库常用的两把锁之乐观锁和悲观锁,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • SpringBoot集成Redisson实现消息队列的示例代码

    SpringBoot集成Redisson实现消息队列的示例代码

    本文介绍了如何在SpringBoot中通过集成Redisson来实现消息队列的功能,包括RedisQueue、RedisQueueInit、RedisQueueListener、RedisQueueService等相关组件的实现和测试,感兴趣的可以了解一下
    2024-10-10
  • 一篇清晰易懂的Java 8彻底卸载与清理教程

    一篇清晰易懂的Java 8彻底卸载与清理教程

    这篇文章主要介绍了Java 8彻底卸载与清理的相关资料,包括卸载程序、删除残留文件、清理环境变量及注册表,最后通过命令验证卸载是否成功,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-05-05

最新评论