介绍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 接口之间的差异,尤其是异常处理在实际项目中非常有用。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值