下面的程序有什么问题吗?
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抛出。
分享到:
相关推荐
10. **并发工具类**:了解Semaphore、CountDownLatch、CyclicBarrier、ThreadPoolExecutor等并发工具的使用。 11. **IO流**:理解字节流和字符流的区别,知道NIO(非阻塞IO)与BIO(阻塞IO)的区别,以及缓冲流的...
- 讨论并发工具类,如Semaphore、CyclicBarrier、CountDownLatch、Exchanger、ThreadPoolExecutor等。 4. **JVM内部机制**: - 理解JVM内存模型(堆、栈、方法区、本地方法栈、程序计数器)及其作用。 - 分析类...
它深入探讨了Java内存模型(JMM)和volatile、synchronized关键字的工作原理,以及如何避免并发编程中的常见陷阱。此外,书中还提供了大量的示例代码和最佳实践,帮助读者提升编写高效、安全的多线程程序的能力。 ...
- **`Executor`框架与`ThreadPoolExecutor`:** 介绍`Executor`框架的基本概念和组成部分,重点讲解`ThreadPoolExecutor`类的配置和使用方法。 - **`Future`与`Callable`:** 理解`Future`接口的用途以及如何使用`...
- **2.4 使用锁保护状态**:讨论了如何使用锁来确保对共享状态的安全访问,包括如何避免常见的陷阱和错误。 - **2.5 活跃性和性能**:进一步探讨了并发程序设计中的活跃性和性能问题,包括如何避免死锁和其他性能...
这本书详细介绍了Java并发编程的基础知识,高级技巧以及实战经验,旨在帮助读者掌握如何有效地利用多核处理器的能力,提高程序性能,同时避免并发编程中常见的陷阱。 并发编程是现代软件开发中的重要组成部分,特别...
本书详细介绍了如何在Java环境中有效地设计和实现并发程序,帮助开发者避免常见的陷阱和错误。 首先,书中详细讲解了Java并发的基础概念,包括线程的创建、生命周期管理以及线程间通信。Java提供了多种方式来创建...
另外,Java并发工具类如Semaphore(信号量)、CyclicBarrier(回环栅栏)、CountDownLatch(计数器)和ThreadPoolExecutor(线程池)等也是书中的重要内容。这些工具能够有效地管理和控制并发任务,提高系统的并发...
这本书深入浅出地探讨了Java平台上的多线程和并发编程技术,对于理解和掌握Java并发编程的核心概念、最佳实践以及常见陷阱具有极大的指导价值。 书中涵盖了以下几个重要的知识点: 1. **并发基础**:介绍并发编程...
书中的内容既包含了理论知识,也提供了丰富的实战案例,旨在帮助开发者避免并发编程中的常见陷阱,提升程序的性能和可维护性。 首先,书中详细阐述了Java并发的基础概念,如线程、进程、同步和异步操作。线程是并发...
6. **并发专题完整代码**:这些文件(如`juc-demo`, `juc-demo1107`, `juc-demo`, `jucdemo`, `jucdemo(1)`等)很可能包含了各种并发编程的示例代码,可以用来学习和理解并发编程的各种实践技巧和陷阱。 7. **...
大鹏的"JAVA基础易错总结"涵盖了这些重要的知识点,并且可能还揭示了一些鲜为人知的陷阱。通过深入学习和实践,开发者可以避免这些常见错误,提升Java编程的水平。希望这份资料能成为你Java学习旅程中的宝贵财富。
5. **线程池**:ExecutorService和ThreadPoolExecutor是Java中实现线程池的基石,通过合理配置线程池,可以有效地管理线程资源,提高系统性能。 6. **死锁、活锁与饥饿**:理解并发中的这些潜在问题至关重要,包括...
本电子书详细介绍了如何在Java环境中有效地使用多线程,优化程序性能,并避免并发编程中的常见陷阱。 在Java中,线程是程序执行的独立路径,它们可以同时运行,使程序能够处理多个任务。Java提供了一个丰富的线程...
2. **线程管理**:讨论如何创建、启动、停止和控制线程,包括线程池的使用,如`ExecutorService`和`ThreadPoolExecutor`,以及线程的优先级和中断机制。 3. **并发控制**:讲解Java中的同步机制,如`synchronized`...
2. **线程的创建与管理**:介绍`Thread`类的使用,包括`Runnable`接口和`Callable`接口,以及线程池(`ExecutorService`、`ThreadPoolExecutor`、`Executors`)的使用方法,如何控制线程的启动、暂停、恢复和终止。...
6. **线程池**:ExecutorService和ThreadPoolExecutor是Java线程池的主要实现,它们可以帮助开发者更好地管理和调度线程,防止过多线程导致的资源浪费。 7. **并发编程最佳实践**:书中还将涵盖线程池的最佳配置、...
其中的"并发编程面试专题.pdf"很可能包含了各种常见问题、概念解释、最佳实践以及陷阱案例。 并发编程的基础知识主要包括以下几个方面: 1. **线程与进程**:线程是操作系统分配CPU时间的基本单位,而进程则是拥有...
5. **线程池**:ExecutorService和ThreadPoolExecutor是Java线程池的主要实现,通过合理配置线程池可以优化系统性能,防止大量线程创建和销毁带来的开销。理解线程池的工作原理和参数设置。 6. **并发工具类**:...