java callable执行过程

前言
我们常用的创建线程方式一般有下面 2 种:

继承Thread,重写run方法
实现Runnable接口,重新run方法
其实在 Executor 框架中还有一种方法可以实现异步,那就是实现 Callable 接口并重写call方法。虽然是实现 Callable ,但是在 Executor 实际运行时,会将 Runnable 的实例或 Callable 的实例转化为 RunnableFuture 的实例,而 RunnableFuture 继承了 Runnable 和 Future 接口,这点将在下文详细解释。了解到这些 ,那么它和 Runnable 有什么不同呢? Callable 与 Runnable 相比有以下 2 点不同:

Callable 可以在任务结束的时候提供一个返回值,Runnable 无法提供这个功能
Callable 的 call 方法分可以抛出异常,而 Runnable 的 run 方法不能抛出异常。


实现原理
在介绍 Callable 的实现原理前,我们先看看它是怎么使用的:

public class ThreadTest {

    public static void main(String[] args) {
        System.out.println("main start");
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        // Future<?> future = threadPool.submit(new MyRunnable()) ;
        Future<String> future = threadPool.submit(new MyCallable());
        try {
            // 这里会发生阻塞
            System.out.println(future.get());
        } catch (Exception e) {
        } finally {
            threadPool.shutdown();
        }
        System.out.println("main end");
   }
}


public class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        // 模拟耗时任务
        Thread.sleep(3000);
        System.out.println("MyCallable 线程:" + Thread.currentThread().getName());
        return "MyCallable" ;
    }
}

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        // 模拟耗时任务
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("MyRunnable");
    }
}

 

运行上面的代码你将得到如下结果:

main start
// 这里会阻塞一段时间,才会打印下面的内容
MyCallable 线程:pool-1-thread-1
MyCallable
main end

 



通过上面的代码我们验证了 Callable 和 Runnable 的不同点,那么 Callable 是怎么实现的呢?首先,我们将 Callable 的实现类 MyCallable 传递给了 ExecutorService.submit() 而 ExecutorService 是一个接口,那么其必将在它的实现类中覆写 submit() 方法;跟进 :Executors.newSingleThreadExecutor()

 

 

 

(图一)

我们发现 ExecutorService 的实现类是 FinalizableDelegatedExecutorService ,再跟进:

 

 

 

(图二)

我们发现 FinalizableDelegatedExecutorService 中没有 submit() 方法,那么其必在父类中实现了该方法,在跟进父类:

 

 

 

(图三)

从 DelegatedExecutorService 中我们可以看出其 submit() 方法的实现调用了 ExecutorService.submit(),是不是感觉又回到原点了,循环调用?当然不是这样的,注意这里的 e.submit 是 DelegatedExecutorService 的一个局部变量 ExecutorService 的方法,而 e 又是通过构造方法赋值的。现在让我们回到 DelegatedExecutorService 的子类 FinalizableDelegatedExecutorService 看看构造方法中传递的是什么,从图二和图一我们可以看到,该构造方法中传递的 ExecutorService 实现类是 ThreadPoolExecutor 。终于找到正主了,让我们跟进 ThreadPoolExecutor 是如何实现 submit() 的,跟进后你会发现 ThreadPoolExecutor 中没有 submit() 方法,那我们只好再到父类中找了:

 

 

 

(图四)

 

 

 

(图五)

从 AbstractExecutorService 代码我们可以看出 Runnable 接口 和 Callable 接口的处理方式一样都是将其转换为 RunnableFuture 。而 RunnableFuture 是一个接口,那么其必有实现类来完成这个转换过程,让我们分别跟进这两个 newTaskFor 看看 Runnable 和 Callable 的实例都是怎么被转换的:

 

 

 

(图六)

从代码中我们可以看到 newTaskForjia() 方法的返回值是 FutureTask 类型的,再次跟进:

 

 

 

(图七)

FutureTask 的这两个构造方法的作用是为 callable 和 state 赋值,到此 Callable 和 Runnable 实例的处理方式一样了;不同的是 Runnable 的实例要传递给 Executors.callable 用于生成 Callable 的实例。下面我们看看这个转化的过程:

 

 

 

(图八)

RunnableAdapter 是 Callable 接口的一个实现类:

(图九)

RunnableAdapter 覆写了 Callable 的 call() 方法,并在执行 call() 时执行了 Runnable 的 run() 方法;这样就做得到了 Runnable 和 Callable 的统一。

既然 Runnable 和 Callable 统一了,那么我们再回头看看线程的执行方法 图五 的 execute(Runnable runnable) 是怎么实现的。 什么情况? execute 传递的是 Runnable 实例,而我们把 Runnable 和 Callable 统一成 Callable 了。是不是感觉很奇怪?注意 图五 中 execute 传递的是 RunnableFuture 的实现类 FutureTask 的实例。而 RunnableFuture 实现了 Runnable 接口,并覆写了 Runnable 的 run() 方法:

 

 

 

(图十)

那么,Runnable 的实现类 FutureTask 是怎么实现 run() 方法的呢?

 

 

 

(图十一)

run() 方法中调用了 Callable.call() , 而 run() 又是覆写的 Runnable 的 run() 方法,到此就理解为什么 图五 的 execute() 方法可以传 RunnableFuture 实例了: 先将 Runnable 或 Callable 的实例统一为 Callable 类型,再在执行 run() 方法时调用 Callable 的 call() 方法。

细心的读者可能发现了文章开头的 demo 中有句注释(// 这里会发生阻塞),那是什么原因导致阻塞呢?让我们跟进 Future 的实现类 FutureTask (由 图五 可以得到) 看看其 get() 方法是怎么实现的:

 

 

 

(图十二)

继续跟进:

 

 

 

(图十三)

awaitDone 是一个死循环,只有当待执行的 Callable 或 Runnable 结束时,才会跳出,这样就不难理解 图十二 的逻辑了,当 Callable 或 Runnable 已经结束时,直接给出返回值,否则就阻塞在 awaitDone() 方法,所以才有注释中那句注释。

 

结语
本文到此已经大致把 Callable 实现异步的原理讲解清楚了,为了更方便的理解,在这里补一张使用 Callable 的流程图。

 

 

 

 

附录
关于 Executor 是属于线程池的内容,不是本文重点,只需要知道:线程池在使用中要接收 Runnable 实例即可。

 

本文摘自原文,如有侵权,请联系我!!!

posted @ 2022-03-01 11:45  高压锅里的大萝卜  阅读(691)  评论(0编辑  收藏  举报