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
接口之间的差异。
评论区