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> { 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 强大。
根据不同的场景选择不同的实现方式。
作者:fw19940314
链接:https://juejin.cn/post/7031514535743733796