血的教训--如何正确使用线程池submit和execute方…
2019-08-16 09:26:13来源:博客园 阅读 ()
血的教训--如何正确使用线程池submit和execute方法
血的教训之背景:使用线程池对存量数据进行迁移,但是总有一批数据迁移失败,无异常日志打印
凶案起因
? 听说parallelStream
并行流是个好东西,由于日常开发stream
串行流的场景比较多,这次需要写迁移程序刚好可以用得上,那还不赶紧拿来装*一下,此时不装更待何时。机智的我还知道在 JVM 的后台,使用通用的 fork/join 池来完成上述功能,该池是所有并行流共享的,默认情况,fork/join 池会为每个处理器分配一个线程,对应的变通方案就是创建自己的线程池如
ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
pool.submit(() -> {
list.parallelStream().collect(Collectors.toList());
});
? 于是地雷就是从这里埋下的。
submit还是execute
public static void main(String[] args) throws InterruptedException, ExecutionException {
final ExecutorService pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
List<Integer> list = Lists.newArrayList(1, 2, 3, null);
//1.使用submit
pool.submit(() -> {
list.parallelStream().map(a -> a.toString()).collect(Collectors.toList());
});
TimeUnit.SECONDS.sleep(3);
//2.使用 execute
pool.execute(() -> {
list.parallelStream().map(a -> a.toString()).collect(Collectors.toList());
});
//3.使用submit,调用get()
pool.submit(() -> {
list.parallelStream().map(a -> a.toString()).collect(Collectors.toList());
}).get();
TimeUnit.SECONDS.sleep(3);
}
? 读者自行跑一下上面的用例,会发现单独使用submit
方法的并不会打印出错误日志,而使用execute
方法打印出了错误日志,但是对submit
返回的FutureJoinTask
调用get()
方法,又会抛出异常。于是真相大白,部分批次中的数据存在脏数据,为null值,遍历到该null值的时候出现了异常,但是异常日志在submit
方法中给catch住,没有打印出来(心痛的感觉),而被捕获的异常,被包装在返回的结果类FutureJoinTask
中,并没有再次抛出。
如果不需要异步返回结果,请不要用submit
方法
? 结论先行,我犯的错误就是,浅显的认为submit
和execute
的区别就只是一个有返回异步结果,一个没有返回一步结果,但是事实是残酷的。在submit()
中逻辑一定包含了将异步任务抛出的异常捕获,而因为使用方法不当而导致该异常没有再次抛出。
? 现在提出一个问题,ForkJoinPool#submit()
中返回的ForkJoinTask
可以获取异步任务的结果,现这个异步抛出了异常,我们尝试获取该任务的结果会是如何? 我们直接看ForkJoinTask#get()
的源码。
public final V get() throws InterruptedException, ExecutionException {
int s = (Thread.currentThread() instanceof ForkJoinWorkerThread) ?
doJoin() : externalInterruptibleAwaitDone();
Throwable ex;
if ((s &= DONE_MASK) == CANCELLED)
throw new CancellationException();
//这里可以直接看到,异步任务出现异常会在调用get()获取结果的时候,会被包装成ExecutionException再次抛出
if (s == EXCEPTIONAL && (ex = getThrowableException()) != null)
throw new ExecutionException(ex);
return getRawResult();
}
? 异步任务出现异常会在调用get()获取结果的时候,会被包装成ExecutionException
再次抛出,但是异常是在哪里被捕获的呢?万变不离其宗,所有线程的线程都需要重写Thread#run()
方法, 投递到ForkJoinPool
的线程会被包装成ForkJoinWorkerThread
,因此我们看一下ForkJoinWorkerThread#run()
的实现.
public void run() {
if (workQueue.array == null) { // only run once
Throwable exception = null;
try {
onStart();
pool.runWorker(workQueue);
} catch (Throwable ex) {
//出现异常,捕获,再次抛出会在调用ForkJoinTask#get()的时候
exception = ex;
} finally {
try {
onTermination(exception);
} catch (Throwable ex) {
if (exception == null)
exception = ex;
} finally {
pool.deregisterWorker(this, exception);
}
}
}
}
? 上面的分析是基于ForkJoinPool
的,是不是所有的线程池的submit
和execute
方法的实现都是类似这样,我们常用的线程池ThreadPoolThread
实现会是怎样的,同样的思路,我们需要找到投递到ThreadPoolThread
的异步任务最终被包装为哪个Thread
的子类或者是实现java.lang.Runnable#run
,答案就是java.util.concurrent.FutureTask
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);
}
}
....
}
总结
? java.util.concurrent.ExecutorService#submit(java.lang.Runnable)
为何线程池会有这种设定,实际上我们的思路不应该局限于线程池,而是放在获取异步任务结果,异常是否也是属于异步结果,FutureTask
作为JDK提供的并发工具类的实现中,已经给出了很好的答案,即获取异步任务结果,异常也是属于异步结果,如果异步任务出现运行时异常,那么在获取该任务的结果时,该异常会被重新包装抛出
? 作者:plz叫我红领巾???
? 出处:https://juejin.im/post/5d15c430f265da1bab29c1fe
??本博客欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。码子不易,您的点赞是我习作最大的动力
原文链接:https://www.cnblogs.com/xxzhuang/p/11103159.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- Flink 如何分流数据 2020-06-11
- 老板让你抗住千万级流量,如何做架构设计? 2020-06-11
- 如何干掉 if else 策略+工厂 2020-06-11
- 类的继承,方法重新中修饰符如何定义 2020-06-10
- 如何写出高质量Spring 组件? 2020-06-08
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash