Callable 与 Runnable 区别

Callable 与 Runnable 区别

Runnable的特点

首先 Runnable接口我们使用的过程中,只需实现接口且实现run()方法,run方法没有方法的返回值。就是说我们只能在run方法体中写我们的异步逻辑,而不能获取返回值。如果想在获取执行结果,因为无法拿到独立线程的结果,方法还是很繁琐的。需要维护一个共享变量,来处理结果。 第二点,run方法不能抛出非运行时异常,run方法上不能 throws 异常,也就是说如果是非运行是异常的场景,写代码时必须要通过try-catch的方式,如下代码:

class Task implements Runnable {
   
   @Override
   public void run() {
       try {
           throw new IOException();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
}
复制代码

如果是运行时异常 RuntimeException,正常使用的场景可以不抛出,如果放在run方法中会影响我们线程的执行。比如我们现在将实现 Runnable接口的任务,使用线程池的方式,定时的执行。如果内部 实现 Runnable 接口。

class Task implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getId() + ":" + Thread.currentThread().getName() + ":" + "task....");
            throw new RuntimeException();
    }
}
复制代码

main

public static void main(String[] args) {
    ScheduledExecutorService service = new ScheduledThreadPoolExecutor(1);
    service.scheduleWithFixedDelay(new Task(), 1, 1, TimeUnit.SECONDS);
}
复制代码

不会继续往后执行定时任务,且不会抛出指定的异常。如果这里写的是我们正常的业务代码是很难定位到问题。

还有一种方式,就是和检查异常一样。抛出我们捕获的异常

class Task implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getId() + ":" + Thread.currentThread().getName() + ":" + "task....");
        try {
            throw new RuntimeException();
        } catch (RuntimeException e){
            throw new RuntimeException();
        }
    }
}
复制代码

上面就是Runable接口的可以说是缺陷。为什么会有此问题,来看一下 Runnable 接口的定义:

public interface Runnable {

   public abstract void run();

}
复制代码

Runnable 是一个 interface,并且里面只有一个方法,叫作 public abstract void run()。这个方法已经规定了 run() 方法的返回类型是 void,而且这个方法没有声明抛出任何异常。所以,当实现并重写这个方法时,我们既不能改返回值类型,也不能更改对于异常抛出的描述,因为在实现方法的时候,语法规定是不允许对这些内容进行修改的。

  • Runnable 为什么设计成这样

再深入思考一层,为什么 Java 要把它设计成这个样子呢?

假设 run() 方法可以返回返回值,或者可以抛出异常,也无济于事,因为我们并没有办法在外层捕获并处理,这是因为调用 run() 方法的类(比如 Thread 类和线程池)是 Java 直接提供的,而不是我们编写的。

所以就算它能有一个返回值,我们也很难把这个返回值利用到,如果真的想弥补Runnable的这两个缺陷,可以用下面的补救措施——使用Callable

Callable 接口

Callable 是一个类似于 Runnable 的接口,实现 Callable 接口的类和实现 Runnable 接口的类都是可以被其他线程执行的任务。 我们看一下 Callable 的源码:

public interface Callable<V{

     call() throws Exception;

}
复制代码

可以看出它也是一个 interface,并且它的 call 方法中已经声明了 throws Exception,前面还有一个 V 泛型的返回值,这就和之前的 Runnable 有很大的区别。实现 Callable 接口,就要实现 call 方法,这个方法的返回值是泛型 V,如果把 call 中计算得到的结果放到这个对象中,就可以利用 call 方法的返回值来获得子线程的执行结果了。

Callable 和 Runnable 的不同之处

总结一下 Callable 和 Runnable 的不同之处:

  • 方法名,Callable 规定的执行方法是 call(),而 Runnable 规定的执行方法是 run();
  • 返回值,Callable 的任务执行后有返回值,而 Runnable 的任务执行后是没有返回值的;
  • 抛出异常,call() 方法可抛出异常,而 run() 方法是不能抛出受检查异常的;
  • 和 Callable 配合的有一个 Future 类,通过 Future 可以了解任务执行情况,或者取消任务的执行,还可获取任务执行的结果,这些功能都是 Runnable 做不到的,Callable 的功能要比 Runnable 强大。

根据不同的场景选择不同的实现方式。

猜你喜欢

转载自juejin.im/post/7031514535743733796