Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java线程池异常处理

Java线程池异常处理

作者头像
leobhao
发布于 2023-03-11 09:11:50
发布于 2023-03-11 09:11:50
42900
代码可运行
举报
文章被收录于专栏:涓流涓流
运行总次数:0
代码可运行

线程池运行中线程异常后的情况

先来看两段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
executorService.execute(() -> {
    int i = 1 / 0;
    System.out.println("execute 执行");
});

====== 输出如下:
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
executorService.submit(() -> {
    int i = 1 / 0;
    System.out.println("submit 执行");
});
======== 输出空
  1. 当执行方式是 execute 时, 可以看到堆栈异常输出
  2. 当执行方式是 submit 时, 不会有堆栈异常
原理探究

ThreadPoolExecutorexecute 方法不用过多分析, 就是线程池的执行流程, 这里看看 submit:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
// 包装成 FutureTask
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

可以看到这里把提交的任务包装成了一个 FutureTask

回到线程池运行流程中的 runWorker中任务运行的一段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
try {
        beforeExecute(wt, task);
        Throwable thrown = null;
        try {
        // 实际逻辑还是 task 本身 run 方法
            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);
        }
    } finally {
        task = null;
        w.completedTasks++;
        w.unlock();
    }

这里可以看到, 其实还是调用 task 本身的 run 方法, 如果 task 本身没有捕捉异常, 最终还是会抛出去的, 前面可以看到使用 submit 的方式是包装为了 futureTask, run方法逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// FutureTask#run 
public void run() {
    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);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}
// 存放异常信息
protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    // outcome 变量保存异常信息
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

可以看到, FutureTask 把异常捕获了, 并未抛出, 只是通过 setException 将异常信息存在了 FutureTaskoutcome 变量里面, 这里也就明白了为什么 submit 不会有异常

那么在看看 future.get:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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();
     // 走到这里也就是状态为 EXCEPTIONAL
    throw new ExecutionException((Throwable)x);
}

可以看到如果 future 的状态是非正常的, 就会将异常包装成 ExecutionException 抛出, 这里也是 submit 可以通过 future.get 获取异常的原理(实际上拿到的是包装完后的 ExecutionException)

从上面的内容我们知道了, submit 把线程池运行过程中产生的异常包装到了 FutureTask 的 outcome 变量里面, 这样我们就可以在线程池外包去捕获异常了, 代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
try {
    Future<String> f = executorService.submit(() -> {
        int i = 1 / 0;
        System.out.println("submit 执行");
        return "success";
    });
    f.get();
} catch (Exception e) {
    System.out.println("submit future get exeception:" + e.getMessage());
}
===== 输出如下:
submit future get exeception:java.lang.ArithmeticException: / by zero

这样就能再线程池外感知到线程池内部发生的异常了(正常情况下, 子线程的异常父线程是无法感知到的)

invokeAll 的陷阱

这里再来看一段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<Callable<String>> callableLists = new ArrayList<>();
callableLists.add(() -> {
    int i = 1/0;
    System.out.println("callable 执行");
   return "success";
});
try {
    executorService.invokeAll(callableLists);
} catch (InterruptedException e) {
    System.out.println("interrupted");
} catch (Exception e) {
    System.out.println("catch exception:" + e.getMessage());
}

=========== 输出空(无打印任何信息)

这里看到 callableLists 这个任务集合中有抛出异常, 那么也无法感知到。 结合我们上面说的 futureTask 把所有异常都包装成了 ExecutionException, 来看看 invokeAll 执行任务的实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
    throws InterruptedException {
    if (tasks == null)
        throw new NullPointerException();
    ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
    boolean done = false;
    try {
    // 全部包装成 future, 然后通过 execute 执行, 这里和 submit 有点像
        for (Callable<T> t : tasks) {
            RunnableFuture<T> f = newTaskFor(t);
            futures.add(f);
            execute(f);
        }
        // 拿到每个 future 的执行结果(也可以是等待每个 future 执行结束)
        for (int i = 0, size = futures.size(); i < size; i++) {
            Future<T> f = futures.get(i);
            if (!f.isDone()) {
                try {
                    f.get();
                } catch (CancellationException ignore) {
                // 这里 catch 到了 ExecutionException, 什么也没处理
                } catch (ExecutionException ignore) {
                }
            }
        }
        done = true;
        return futures;
    } finally {
        if (!done)
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
    }
}

可以看到ExecutionException被 catch 到后什么也没处理(ignore 了)

感知子线程内部异常方式

原子变量传递
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
AtomicBoolean exception = new AtomicBoolean(false);

Callable<Void> qwbkt = () -> {
    try {
        qwbktSections.add(qwbktManager.query(context, null));
    } catch (Throwable t) {
        context.getLogger().error("qwbkt exception:", t);
        exception.set(true);
    }
    return null;
};

// 查看原子变量是否状态被设置
if (exception.get()) {
    throw new RuntimeException("queryError");
}
通过 code 传递
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Callable<String> task = new Callable<String>() {
    @Override
    public String call() throws Exception {
        Result<String> result = new Result<>();
        try {
            // 将结果包裹起来, 设置到 code 中
        } catch (Exception e) {
            result.setCode("500");
        }
        return result;
    }
};
future.get

最常见的还是通过 future.get

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
try {
    String s = future.get();
} catch (InterruptedException e) {
    //..
} catch (ExecutionException e) {
    //todo: 这里处理线程内部异常
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年4月30日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java线程池异常处理
ThreadPoolExecutor 的 execute 方法不用过多分析, 就是线程池的执行流程, 这里看看 submit:
leobhao
2022/06/28
3830
线程池中线程抛了异常,该如何处理?
在实际开发中,我们常常会用到线程池,但任务一旦提交到线程池之后,如果发生异常之后,怎么处理? 怎么获取到异常信息?在了解这个问题之前,可以先看一下 线程池的源码解析,从源码中我们知道了线程池的提交方式:submit和execute的区别,接下来分别使用他们执行带有异常的任务!看结果是怎么样的!
码猿技术专栏
2023/05/01
6810
线程池中线程抛了异常,该如何处理?
字节二面:线程池中线程抛了异常,该如何处理?
异常处理大家应该很熟了。但有些事务我们需要跑在线程池里,这种异常处理应该如何实现?
java思维导图
2023/11/02
4.1K0
字节二面:线程池中线程抛了异常,该如何处理?
JUC线程池服务ExecutorService接口实现源码分析
之前的一篇文章JUC线程池ThreadPoolExecutor源码分析深入分析了JUC线程池的源码实现,特别对Executor#execute()接口的实现做了行级别的源码分析。这篇文章主要分析一下线程池扩展服务ExecutorService接口的实现源码,同时会重点分析Future的底层实现。ThreadPoolExecutor和其抽象父类AbstractExecutorService的源码从JDK8到JDK11基本没有变化,本文编写的时候使用的是JDK11,由于ExecutorService接口的定义在JDK[8,11]都没有变化,本文的分析适用于这个JDK版本范围的任意版本。最近尝试找Hexo可以渲染Asciidoc的插件,但是没有找到,于是就先移植了Asciidoc中的五种Tip。
Throwable
2020/06/23
6800
JUC线程池服务ExecutorService接口实现源码分析
死磕 java线程系列之线程池深入解析——未来任务执行流程
前面我们一起学习了线程池中普通任务的执行流程,但其实线程池中还有一种任务,叫作未来任务(future task),使用它您可以获取任务执行的结果,它是怎么实现的呢?
彤哥
2019/11/06
5670
死磕 java线程系列之线程池深入解析——未来任务执行流程
我靠(call) ,我的未来(Future)在哪里???
大家好,我是 cxuan,之前一直在分享操作系统相关的文章,兜兜转转回到了 Java 文章分享,本篇文章是读者投稿,来和你一起聊一聊 Future ~
cxuan
2020/08/02
5560
深度解读 java 线程池设计思想及源码实现
转自:https://javadoop.com/2017/09/05/java-thread-pool
Java技术江湖
2019/09/25
6570
线程池续:你必须要知道的线程池submit()实现原理之FutureTask!
FutureTask思维导图.png 前言 上一篇内容写了Java中线程池的实现原理及源码分析,说好的是实实在在的大满足,想通过一篇文章让大家对线程池有个透彻的了解,但是文章写完总觉得还缺点什
一枝花算不算浪漫
2020/06/01
2K0
(77) 异步任务执行服务 / 计算机程序的思维逻辑
Java并发包提供了一套框架,大大简化了执行异步任务所需的开发,本节我们就来初步探讨这套框架。 在之前的介绍中,线程Thread既表示要执行的任务,又表示执行的机制,而这套框架引入了一个"执行服务"的概念,它将"任务的提交"和"任务的执行"相分离,"执行服务"封装了任务执行的细节,对于任务提交者而言,它可以关注于任务本身,如提交任务、获取结果、取消任务,而不需要关注任务执行的细节,如线程创建、任务调度、线程关闭等。 以上描述可能比较抽象,接下来,我们会一步步具体阐述。 基本接口 首先,我们来看任务执行
swiftma
2018/01/31
8200
Java并发指南12:深度解读 java 线程池设计思想及源码实现
本文是微信公众号【Java技术江湖】的《Java并发指南》其中一篇,本文大部分内容来源于网络,为了把本文主题讲得清晰透彻,也整合了很多我认为不错的技术博客内容,引用其中了一些比较好的博客文章,如有侵权,请联系作者。
Java技术江湖
2019/11/20
6360
Java并发指南12:深度解读 java 线程池设计思想及源码实现
futureTask的超时原理解析
java/util/concurrent/AbstractExecutorService.java
code4it
2018/09/17
8500
高并发之——P8级别架构师带你深度解析线程池中那些重要的顶层接口和抽象类
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:
冰河
2020/10/29
5100
高并发之——P8级别架构师带你深度解析线程池中那些重要的顶层接口和抽象类
ExecutorService、Callable、Future实现有返回结果的多线程原理解析
在并发多线程场景下,存在需要获取各线程的异步执行结果,这时,就可以通过ExecutorService线程池结合Callable、Future来实现。
朱季谦
2022/09/25
8980
ExecutorService、Callable、Future实现有返回结果的多线程原理解析
Java并发编程学习11-任务执行演示
开始之前,引用一篇《从入门到实战学习ES》,该文从ES的背景、概念、工具、知识点、部署、实战、Java开发、分布式节点、底层逻辑以及分词分类等方面进行了详细梳理和介绍,有需要的朋友可以研究下!
huazie
2024/11/12
1310
Java并发编程学习11-任务执行演示
死磕 java线程系列之线程池深入解析——体系结构
Java的线程池是块硬骨头,对线程池的源码做深入研究不仅能提高对Java整个并发编程的理解,也能提高自己在面试中的表现,增加被录取的可能性。
彤哥
2019/10/15
4350
死磕 java线程系列之线程池深入解析——体系结构
接口经常超时?线程池+ FutureTask来解决!
之前红包权益领取查询的接口超时了,因为有用户订购的权益有点多 解决方案 用线程池+ FutureTask将1个查询拆分成多个小查询 选择FutureTask是因为它具有仅执行1次run()方法的特性(即使有多次调用也只执行1次),避免了重复查询的可能。而且多任务异步执行也能提高接口响应速度。 本文主要讲的是线程池搭配FutureTask异步执行的例子 线程池 + FutureTask执行多任务计算 public class Test {  //线程池最好作为全局变量, 若作为局部变量记得用完后shutdow
程序猿DD
2022/03/04
6830
【原创】Java并发编程系列36 | FutureTask
线程池源码中出现了很多Callable、Future、FutureTask等以前没介绍过的接口,尤其是线程池提交任务时总是把任务封装成FutureTask,今天就来为大家解惑:
java进阶架构师
2020/09/22
3540
【原创】Java并发编程系列36 | FutureTask
透彻Java线程池的实现原理
其实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。
挨踢小子部落阁
2023/03/15
2950
透彻Java线程池的实现原理
Java Review - 线程池使用FutureTask的小坑
线程池使用FutureTask时如果把拒绝策略设置为 DiscardPolicy和 DiscardOldestPolicy,并且在被拒绝的任务的Future对象上调用了无参get方法,那么调用线程会一直被阻塞。
小小工匠
2021/11/22
4960
Java Review - 线程池使用FutureTask的小坑
13.ThreadPoolExecutor线程池之submit方法
jdk1.7.0_79   在上一篇《ThreadPoolExecutor线程池原理及其execute方法》中提到了线程池ThreadPoolExecutor的原理以及它的execute方法。本文解析ThreadPoolExecutor#submit。   对于一个任务的执行有时我们不需要它返回结果,但是有我们需要它的返回执行结果。对于线程来讲,如果不需要它返回结果则实现Runnable,而如果需要执行结果的话则可以实现Callable。在线程池同样execute提供一个不需要返回结果的任务执行,而对于需
用户1148394
2018/01/12
2.6K0
13.ThreadPoolExecutor线程池之submit方法
推荐阅读
相关推荐
Java线程池异常处理
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验