天天看点

介绍java 中 Runnable 和 Callable

介绍java 中 Runnable 和 Callable

从java早期开始,多线程已经就是其主要特性之一。Runable接口是表现多线程任务核心接口,Callable是java1.5之后引入的新街口。

本文,我们探讨下这两个接口之间的差别。

## 执行机制

这两个接口都代表能被多线程执行的任务,Runable任务可以使用Thread和ExecutorService执行,而Callable只能使用后者执行。

返回值

让我们深入探讨这些接口处理的返回值。

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对象不包含任何值。

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对象:

@Test
public void whenTaskSubmitted_ThenFutureResultObtained(){
    FactorialTask task = new FactorialTask(5);
    Future<Integer> future = executorService.submit(task);
  
    assertEquals(120, future.get().intValue());
}
           

异常处理

下面我们看如何处理任务执行异常情况:

使用Runnable

因为其run方法没有任何throws子句作为方法签名规范,无法进一步传播检查异常。

使用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会抛出异常。

总结

本文,我们探讨了Runnable 和 Callable 接口之间的差异,尤其是异常处理在实际项目中非常有用。

继续阅读