Baeldung 翻译系列之Java并发基础:
Java中的concurrent包
Java中的Synchronized关键字
Future介绍
ThreadLocal介绍
Java线程的生命周期
如何杀掉一个Java线程
Java中的线程池介绍
实现Runnable接口还是继承Thread类
Java中的wait和notify方法
Runnable vs Callable
wait和sleep的区别
Thread.join方法介绍
Java中使用锁对象
ThreadPoolTaskExecutor中的corePoolSize和maxPoolSize
Java中的异步编程
1. 概览
自Java早期以来,多线程一直是该语言的主要方面。Runnable是为表示多线程任务提供的核心接口,而Java 1.5提供了Callable作为Runnable的改进版本。
在本教程中,我们将探讨这两个接口的区别和应用。
2. 执行机制
这两个接口都设计用来表示可以由多个线程运行的任务。我们可以使用Thread类或ExecutorService来运行Runnable任务,而我们只能使用后者来运行Callable任务。
3. 返回值
让我们深入研究一下这些接口如何处理返回值。
3.1. 使用Runnable
Runnable接口是一个函数式接口,它有一个run()方法,这个方法不接受任何参数,也不返回任何值。
对于我们不寻求线程执行结果的情况,这是可行的,例如记录传入的事件:
public interface Runnable {
public void run();
}
下面是一个例子
public class EventLoggingTask implements Runnable{
private Logger logger
= LoggerFactory.getLogger(EventLoggingTask.class);
@Override
public void run() {
logger.info("Message");
}
}
在这个例子中,线程只会从队列中读取一条消息并将其记录在日志文件中。任务没有返回值。
我们可以使用ExecutorService来启动任务:
public void executeTask() {
executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(new EventLoggingTask());
executorService.shutdown();
}
在这种情况下,Future对象没有持有任何值
3.2. 使用Callable
Callable接口是一个泛型接口,包含一个单一的call()方法,该方法返回一个泛型值V:
public interface Callable<V> {
V call() throws Exception;
}
让我们来看一下如何计算一个数的阶乘:
public class FactorialTask implements Callable<Integer> {
int number;
// standard constructors
public Integer call() throws InvalidParamaterException {
int fact = 1;
// ...
for(int count = number; count > 1; count--) {
fact = fact * count;
}
return fact;
}
}
call()方法的返回一个Future对象, 这个Future对象里面包含的就是最终计算的值
@Test
public void whenTaskSubmitted_ThenFutureResultObtained(){
FactorialTask task = new FactorialTask(5);
Future<Integer> future = executorService.submit(task);
assertEquals(120, future.get().intValue());
}
4. 异常处理
让我们看看它们在处理异常方面的适应性。
4.1. 使用Runnable的情况
由于方法签名没有指定"throws"子句,我们没有办法进一步传播受检异常。
4.2. 使用Callable的情况
Callable的call()方法包含"throws Exception"子句,所以我们可以轻松地进一步传播受检异常:
public class FactorialTask implements Callable<Integer> {
// ...
public Integer call() throws InvalidParamaterException {
if(number < 0) {
throw new InvalidParamaterException("Number should be positive");
}
// ...
}
}
如果使用ExecutorService运行Callable,异常会被收集在Future对象中。我们可以通过调用Future.get()方法来检查这一点。
这将抛出一个ExecutionException,它包装了原始的异常:
@Test(expected = ExecutionException.class)
public void whenException_ThenCallableThrowsIt() {
FactorialCallableTask task = new FactorialCallableTask(-5);
Future<Integer> future = executorService.submit(task);
Integer result = future.get().intValue();
}
在上述测试中,由于我们传入了一个无效的数字,所以抛出了ExecutionException。我们可以在这个异常对象上调用getCause()方法来获取原始的受检异常。
如果我们不调用Future类的get()方法,那么由call()方法抛出的异常将不会被报告回来,任务仍然会被标记为已完成:
@Test
public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){
FactorialCallableTask task = new FactorialCallableTask(-5);
Future<Integer> future = executorService.submit(task);
assertEquals(false, future.isDone());
}
尽管我们对FactorialCallableTask的参数的负值抛出了异常,但上述测试仍然会成功通过。
5. 总结
在这篇文章中,我们探讨了Runnable和Callable接口之间的差异。
评论区