JDK版本:JDK11
1. 背景
在平时的开发过程中线程肯定用不少,线程启动执行需要实现 Runnable
类:
public class ThreadTest { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(111); } }, "Thread-mxsm").start(); }}复制代码
是自己新建一个线程对象,然后执行Runnable 执行完成线程结束。
除了这样的还有使用到线程池,如下:
public class ThreadTest { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.execute(new Runnable() { @Override public void run() { System.out.println(111); } }); }}复制代码
但是在线程池来执行提交任务的时候,你可能注意到了这样情况如下图:
2. Callable与Runnable的关联分析
从上面的列出的三个方面来分析两者之间的关联。
2.1 Callable与Runnable源码分析
Runnable 平时在开发的时候经常用,所以大家也比较熟悉:
@FunctionalInterfacepublic interface Runnable { public abstract void run();}复制代码
只有一个方法 run
。 在源码中的注释中翻译总结一下就是:任何需要由Thread执行的类都需要实现Runnable。
Tips: 所以Callable应该是巧妙的转换成了Runnable
Callable 用的不多,看一下源码:
@FunctionalInterfacepublic interface Callable { V call() throws Exception;}复制代码
Callable 也只有一个方法 call 但是方法有返回值。
从源码上面可以看出来 Callable 和 Runnable 没有任何继承关系,Runnable的方法没有返回值,而Callable的方法有返回值。
2.2 从线程池看Callable如何包装成Runnable
JDK在Runnable的注释上有明确的说明:任何需要由Thread执行的类都需要实现Runnable。所以我们可以推断出来Callable用了某种方式包装成了Runnable。
通过 ExecutorService#submit 方法提交 Callable 来分析:
通过 newTaskFor 方法可以发现,最终是创建了一个 FutureTask 对象,Callable 作为构造函数的参数。那么看一下FutureTask :
public class FutureTask implements RunnableFuture { /** The underlying callable; nulled out after running */ private Callable callable; /** The result to return or exception to throw from get() */ private Object outcome; // non-volatile, protected by state reads/writes /** The thread running the callable; CASed during run() */ private volatile Thread runner; /** Treiber stack of waiting threads */ private volatile WaitNode waiters; public FutureTask(Callable callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable }//省略部分代码}复制代码
Callable作为了FutureTask的一个属性值。之前说过,要想被Thread执行必须实现Runnable。那么我们看一下 FutureTask 的实现类 RunnableFuture :
- FutureTask#run 内部就调用了Callable的call方法
然后由线程池中的Thread去执行FutureTask(也就是Runnable的实现的实例)。上面类的继承关系:
2.3 线程池如何执行Callable有什么特点
将Callable包装成Runnable后,线程池的执行和执行Runnable一样,Callable的特点就是可以获取到返回值。如果执行的逻辑不关心返回值就可以直接用Runnable来。但是如果需要涉及到获取到业务逻辑中的返回值那么就使用Callable来提交到线程池中。
3. 总结
- Runnable和Callable两者没有继承关系,Callable通过FutureTask包装成Runnable。
- 线程池执行任务的时候,如果关系返回值就用Callable,不关心返回值用Runnable。
- Runnable如果也需要返回值,线程池内部是通过RunnableAdapter适配器来适配成Callable