下面的程序有什么问题吗?
package tpe2;
import java.util.concurrent.*;
/**
* 这个作业计算两个整数相除的商和余数,并输出。
*/
class DivideNumbersJob implements Runnable {
int a,b;
public DivideNumbersJob(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
int c = a/b;
int d = a%b;
System.out.format("%d / %d === %d mod %d\n", a, b, c, d);
}
}
public class TPEDemo {
public static void main(String[] args) throws Exception {
ThreadPoolExecutor e = new ThreadPoolExecutor(5, 5, 0,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
// 但是,与上一篇文章不同,我怕这些作业耗时太多,
// 我改用了submit方法,并保留了他们的Future对象。
Future<?> f1 = e.submit(new DivideNumbersJob(6, 2));
Future<?> f2 = e.submit(new DivideNumbersJob(12, 5));
Future<?> f3 = e.submit(new DivideNumbersJob(4, 0)); // 错误的作业!
Future<?> f4 = e.submit(new DivideNumbersJob(100, 3));
// 告诉Executor,没有更多的任务了。
e.shutdown();
// 主线程睡一觉
Thread.sleep(2000);
// 遍历所有的Future对象,如果有没做完的,就取消。
for (Future<?> f : new Future<?>[]{f1,f2,f3,f4}) {
if(!f.isDone()) {
f.cancel(true);
}
}
}
}
注意,我提交了一个错误的作业:new DivideNumbersJob(4, 0)。地球人都知道,除数不能为0。按照常理,这个作业应该会抛出ArithmeticException。但是这是一个RuntimeException,不用在方法的throws部分声明。所以,DivideNumbersJob这个类仍然可以说是“实现了Runnable接口”(如果加上throws ArithmeticException),则编译通不过,原因是与接口的throws部分不匹配。
但是,实际的执行结果仅仅是如下:
引用
6 / 2 === 3 mod 0
100 / 3 === 33 mod 1
12 / 5 === 2 mod 2
没有任何异常!没有任何异常!!控制台干干净净!!!
多么可怕!我不怕程序抛出异常,但是,我最怕的就是,明明抛出了异常,却安静地溜掉,让bug静静地潜伏着。
《Python之禅》有这一句:“Error should never pass silently, unless explicitly silenced.” 参考:
http://www.python.org/dev/peps/pep-0020/
答案:这个异常确实事被捕获了,被存放在Future对象中。
我们稍微修改main方法:
// 主线程睡一觉
Thread.sleep(2000);
// 遍历所有的Future对象,如果有没做完的,就取消。
for (Future<?> f : new Future<?>[]{f1,f2,f3,f4}) {
if(!f.isDone()) {
f.cancel(true);
+ } else {
+ Object result = f.get();
}
}
我们在睡觉之后,判断一下:如果未完成,取消作业;如果完成了,试着获取它的值。我们知道,submit提交Runnable作业,返回值一定是null。但是,如果该作业执行期间有异常,那么,当我们试图调用f.get()方法,获取值的时候,将会抛出ExecutionException。
这是现在执行的结果:
引用
12 / 5 === 2 mod 2
6 / 2 === 3 mod 0
100 / 3 === 33 mod 1
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:252)
at java.util.concurrent.FutureTask.get(FutureTask.java:111)
at tpe2.TPEDemo.main(TPEDemo.java:44)
Caused by: java.lang.ArithmeticException: / by zero
at tpe2.DivideNumbersJob.run(TPEDemo.java:15)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:636)
最后那个ExecutionException正是Future.get()抛出的。它的嵌套的cause就是在我们的DivideNumbersJob.run()中抛出的ArithmeticException。这个对象可以用Exception.getCause方法获得。
而有时候,使用Future对象只是为了方便终止一个作业,而不想去调用Future.get()方法获取异常。这种情况下,干脆把异常在Runnable.run()中彻底解决,以免夜长梦多。
@Override
public void run() {
+ try {
int c = a/b;
int d = a%b;
System.out.format("%d / %d === %d mod %d\n", a, b, c, d);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
}
这样,一旦出现ArithmeticException,就马上会打印出异常。而这样的致命异常也无法再进一步处理了。
另外,如果作业是用Executor.execute(Runnable r);提交的,那么,由于没有Future对象,这个异常不会被存储,而是在执行完毕就被Executor抛出。
分享到:
相关推荐
ThreadPoolExecutor源码解析.pdf
ThreadPoolExecutor使用和思考
ThreadPoolExecutor源码解析.md
ThreadPoolExecutor线程池,有详尽介绍,本人进行过测试,可以使用
(转)线程池:java_util_ThreadPoolExecutor 比较详细的介绍了ThreadPoolExecutor用法与属性
在《阿里巴巴java开发手册》中...另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。
一个关于java 线程池的例子,也适合android
线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor...
ThreadPoolExecutor的使用和Android常见的4种线程池使用介绍
线程池的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,那么超出数量的线程排队等候,等其他线程执行完毕再从队列中取出任务来执行。...
NULL 博文链接:https://bijian1013.iteye.com/blog/2284676
1.资源简介:PyQt5中使用多线程模块QThread解决了PyQt5界面程序执行比较耗时操作时,程序卡顿出现的无响应以及界面输出无法实时显示的问题,采用线程池ThreadPoolExecutor解决了ping多个IP多任务耗时问题。...
线程池原理-ThreadPoolExecutor源码解析 1.构造方法及参数 2.阻塞对列: BlockingQueue 3.线程工厂: DefaultThreadFactory 4.拒绝策略: RejectedExecutionHandler 5.执行线程 Executor
死磕ThreadPoolExecutor线程池.pdf!!死磕ThreadPoolExecutor线程池.pdf死磕ThreadPoolExecutor线程池.pdf死磕ThreadPoolExecutor线程池.pdf
介绍ThreadPoolExecutor中池和queue配合使用的机制
JDK1[1].5中的线程池(ThreadPoolExecutor)使用简介
线程池ThreadPoolExecutor底层原理源码分析
JDK1.5中的线程池(java.util.concurrent.ThreadPoolExecutor)使用
Android中的线程池ThreadPoolExecutor解决了单线程下载数据的效率慢和线程阻塞的的问题,它的应用也是优化实现的方式。所以它的重要性不言而喻,但是它的复杂性也大,理解上可能会有问题,不过作为安卓工程师,了解...