Java并发编程 - Runnbale、Future、Callable 你不知道的那点事(一)大致说明了一下 Runnable、Future、Callable 接口之间的关系,也说明了一些内部常用的方法的含义,那具体内部怎么实现的呢?JDK内部底层源码怎么解读?我就带领大家一一探个究竟。

一、ExecutorService 中常用的 submit() 底层源码详解

(1)ExecutorService 中常用的 submit() 方法展示:

  1. <T> Future<T> submit(Callable<T> task);
  2. Future<?> submit(Runnable task);

   暂时只需要知道线程池中的线程是依赖于 ExecutorService 接口进行调用的,实现类是 ThreadPoolExecutor,重写了 Executor 接口的 execute() 方法,然后进行调用;

  后面我会专门讲解一下 Executor、ExecutorService、AbstractExecutorService、ThreadPoolExecutor、Executors 之间的关系,让你详细了解线程池的原理和概念。

(2)ExecutorService 中的 submit(Runnable task) 方法详解

  首先通过 Executors 工具类方法创建线程池(单线程线程池、固定数量线程池、缓存线程池),然后调用 submit() 方法,传入的参数切记是 Runnable 接口的实现类,肯定是重写了 run() 方法;

  明白上面这句话的原理,看一下 AbstractExecutorService 抽象类中的 submit(Runnable task) 方法详解:一步一步调用

  1. public Future<?> submit(Runnable task) {
  2. if (task == null) throw new NullPointerException();
  3. RunnableFuture<Void> ftask = newTaskFor(task, null);
  4. execute(ftask);
  5. return ftask;
  6. }

  FutureTask 构造方法,设置参数 callable,state 参数:

  1. public FutureTask(Runnable runnable, V result) {
  2. this.callable = Executors.callable(runnable, result);    // 调用线程工具类 Executors 中的 callable() 方法
  3. this.state = NEW;
    }

  Executors 工具类中的 callable() 方法

  1. public static <T> Callable<T> callable(Runnable task, T result) {
  2. if (task == null)
  3. throw new NullPointerException();
  4. return new RunnableAdapter<T>(task, result);
  5. }
  6.  
  7. static final class RunnableAdapter<T> implements Callable<T> {
  8. final Runnable task;
  9. final T result;
  10. RunnableAdapter(Runnable task, T result) {
  11. this.task = task;
  12. this.result = result;
  13. }
  14. public T call() {
  15. task.run();
  16. return result;
  17. }
  18. }

  1)先判断 task 不能为空,然后调用 newTaskFor(task, null) 方法,然后一个 RunabbleFuture 接口的实现类实例对象: FuntureTask 实例对象【切记:这个类肯定重写了 run() 方法】;

  2)注意:创建 FutureTask 对象实例时,调用工具类 Executors 创建一个 RunnableAdapter 对象(可以理解为:Runnable 和Callable 之间的适配器),RunnableAdapter 内部类继承了 Callable 接口,重写了 call() 方法,在 call() 方法中调用真正任务要执行的 run() 方法逻辑;

    3)然后调用 execute(ftask) 方法,这个方法是线程池实现顶层接口 Executor,重写了 execute() 方法,去执行任务逻辑方法;

  ExecutorPoolThread 类中的 execute() 方法:

  1. public void execute(Runnable command) {
  2. if (command == null)
  3. throw new NullPointerException();
  4. int c = ctl.get();
  5. if (workerCountOf(c) < corePoolSize) {
  6. if (addWorker(command, true))
  7. return;
  8. c = ctl.get();
  9. }
  10. if (isRunning(c) && workQueue.offer(command)) {
  11. int recheck = ctl.get();
  12. if (! isRunning(recheck) && remove(command))
  13. reject(command);
  14. else if (workerCountOf(recheck) == 0)
  15. addWorker(null, false); // 添加到工作线程中
  16. }
  17. else if (!addWorker(command, false))
  18. reject(command);
  19. }

  线程池 ExecutorService 直接调用 execute() 方法执行线程任务,则会创建新的 Worker 对象(Worker 类中有一个 Thread 成员变量),Worker 对象可以理解为一个工作线程,是用HashSet() 集合存储,之后该线程调用 start() 方法线程开始执行任务;

  注意:创建 FutureTask 对象实例时,调用工具类 Executors 创建一个 RunnableAdapter 对象(可以理解为:Runnable 和Callable 之间的适配器),RunnableAdapter 内部类继承了 Callable 接口,重写了 call() 方法,在 call() 方法中调用真正任务要执行的 run() 方法逻辑;

  本质:调用FutureTask类的run( )方法,在其run( )方法中调用了RunnableAdapter类的call( )方法,在call( )方法中调用了Runnable实现类的run( ) 方法!

(3)ExecutorService 中的 submit(Callable task) 方法详解

  实现 Callable 接口的任务,在线程池底层原理调用上是比较简单的,没有 submit(Runnable task) 这么复杂;

  AbstractExecutorService 抽象类中的 submit(Callable task) 方法详解:

  1. public <T> Future<T> submit(Callable<T> task) {
  2. if (task == null) throw new NullPointerException();
  3. RunnableFuture<T> ftask = newTaskFor(task);
  4. execute(ftask);
  5. return ftask;
  6. }

  FutureTask 类中设置 callable、state 参数

  1. protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
  2. return new FutureTask<T>(callable);
  3. }
  4.  
  5. public FutureTask(Callable<V> callable) {
  6. if (callable == null)
  7. throw new NullPointerException();
  8. this.callable = callable;
  9. this.state = NEW; // ensure visibility of callable
  10. }

  线程池 ExecutorService 直接调用 execute() 方法执行线程任务,则会创建新的 Worker 对象(Worker 类中有一个 Thread 成员变量),Worker 对象可以理解为一个工作线程,是用HashSet() 集合存储,之后该线程调用 start() 方法线程开始执行任务;

  本质:在调用 ExecutorPoolThread 中的 run() 方法执行任务时,在里面是调用了真正创建的 FutureTask 实例的 run() 方法,在这里面才真正调用了实现 Callable 接口的 call() 方法,call() 方法中的代码就是真正要执行任务的逻辑代码;

二、实践

  讲了这么多原理实在是太枯燥乏味,一时半会也理解不了,咱就结合实践再加深一下印象!

   1)使用 Callable + Future + ExecutorPoolThread 获取执行结果

  1.  
  1. public class Test1 {
    public static void main(String[] args) {
    ExecutorService executor = Executors.newCachedThreadPool();
    Task task = new Task();
    Future<Integer> result = executor.submit(task);
    executor.shutdown();

    try {
    Thread.sleep(1000);
    } catch (InterruptedException e1) {
    e1.printStackTrace();
    }

    System.out.println("主线程执行......");
    try {
    System.out.println("task运行结果" + result.get());
    } catch (Exception e) {
    e.printStackTrace();
    }
    System.out.println("执行完毕");
    }
    }

    class Task implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
    System.out.println("子线程执行......");
    Thread.sleep(3000);
    int sum = 0;
    for (int i = 0; i < 100; i++)
    sum += i;
    return sum;
    }
    }
  1.  

  结果:

  1. 子线程执行......
  2. 主线程执行......
  3. task运行结果4950
  4. 执行完毕

  2)Runnable + ExecutorPoolThread 获取不到结果

  1. public class Test2 {
  2. public static void main(String[] args) {
  3. ExecutorService executor = Executors.newCachedThreadPool();
  4. Task2 task = new Task2();
  5. Future result = executor.submit(task);
  6. executor.shutdown();
  7.  
  8. try {
  9. Thread.sleep(1000);
  10. } catch (InterruptedException e1) {
  11. e1.printStackTrace();
  12. }
  13.  
  14. System.out.println("主线程执行......");
  15. try {
  16. System.out.println("task运行结果" + result.get());
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }
  20. System.out.println("执行完毕");
  21. }
  22. }
  23.  
  24. class Task2 implements Runnable {
  25. @Override
  26. public void run() {
  27. try {
  28. System.out.println("子线程执行......");
  29. Thread.sleep(3000);
  30. Integer sum = 0;
  31. for (int i = 0; i < 100; i++) {
  32. sum += i;
  33. }
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. }

  结果:

  1. 子线程执行......
  2. 主线程执行......
  3. task运行结果null
  4. 执行完毕

  task运行结果是 null,由于不是 Callable 接口的实现类,没有通用性,就使用了 RunnableAdapter 适配器来保证通用性,所以在 RunnableAdapter 实现了 Callable 接口中重写了 call() 方法,由于传入的 result 是 null,所以返回的 result 也就是 null 了。

三、总结

  说了这么多Runnable、Future、Callable 底层调用的实现原理,本质都会创建一个 FutureTask 对象,然后调用 run() 方法,最后再调用 call() 方法实现任务逻辑【若是 Runnable 接口的实例对象,会使用 RunnableAdapter 适配器来转接调用一下,然后在调用 call() 方法,在此 call() 方法里面在调用 run() 方法执行;若是 Callable 接口的实例,直接调用 call() 方法执行】

  菜鸟的简单整理,大家仔细阅读,有问题请留言一起讨论讨论!

Java并发编程 - Runnbale、Future、Callable 你不知道的那点事(二)的更多相关文章

  1. Java并发编程 - Runnbale、Future、Callable 你不知道的那点事(一)

    从事Java开发已经快两年了,都说Java并发编程比较难,比较重要,关键面试必问,但是在我的日常开发过程中,还真的没有过多的用到过并发编程:这不疫情嘛,周末不能瞎逛,就看看师傅们常说的 Runnabl ...

  2. Java并发编程:ThreadPoolExecutor + Callable + Future(FutureTask) 探知线程的执行状况

    如题 (总结要点) 使用ThreadPoolExecutor来创建线程,使用Callable + Future 来执行并探知线程执行情况: V get (long timeout, TimeUnit ...

  3. Java 并发编程:volatile的使用及其原理(二)

    一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果 ...

  4. java并发编程-Executor框架 + Callable + Future

    from: https://www.cnblogs.com/shipengzhi/articles/2067154.html import java.util.concurrent.*; public ...

  5. Java并发编程:Callable、Future和FutureTask

    作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...

  6. java并发编程--Runnable Callable及Future

    1.Runnable Runnable是个接口,使用很简单: 1. 实现该接口并重写run方法 2. 利用该类的对象创建线程 3. 线程启动时就会自动调用该对象的run方法 通常在开发中结合Execu ...

  7. (转)Java并发编程:Callable、Future和FutureTask

    Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...

  8. Java 并发编程:Callable和Future

    项目中经常有些任务需要异步(提交到线程池中)去执行,而主线程往往需要知道异步执行产生的结果,这时我们要怎么做呢?用runnable是无法实现的,我们需要用callable实现. import java ...

  9. Java并发编程:Callable、Future和FutureTask(转)

    Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...

随机推荐

  1. spring boot:给接口增加签名验证(spring boot 2.3.1)

    一,为什么要给接口做签名验证? 1,app客户端在与服务端通信时,通常都是以接口的形式实现, 这种形式的安全方面有可能出现以下问题: 被非法访问(例如:发短信的接口通常会被利用来垃圾短信) 被重复访问 ...

  2. linux(centos8):用uniq去除文本中重复的行(去重)

    一,uniq命令的用途 1, 作用: 从输入文件或标准输入中找到相邻的匹配行, 并写入到输出文件或标准输出 2, 使用时通常会搭配sort使用 说明:刘宏缔的架构森林是一个专注架构的博客,地址:htt ...

  3. monolog记录日志

    <?php require_once 'vendor/autoload.php'; use Monolog\Formatter\LineFormatter; use Monolog\Logger ...

  4. Java爬取同花顺股票数据(附源码)

    最近有小伙伴问我能不能抓取同花顺的数据,最近股票行情还不错,想把数据抓下来自己分析分析.我大A股,大家都知道的,一个概念火了,相应的股票就都大涨. 如果能及时获取股票涨跌信息,那就能在刚开始火起来的时 ...

  5. eclipse配置打开选中文件存储的目录快捷配置

    方便同时复制多个包的文件 https://jingyan.baidu.com/article/adc8151353a896f723bf73cd.html

  6. hive drop和恢复partition external table

    在hdfs目录:/user/xx/table/test_external 保存 test_external 表数据 先建表,使用列式存储格式 CREATE external TABLE `test_e ...

  7. h5 返回上一页面方法

    //以下方法仅供参考1.返回上一页,不刷新history.html window.history.go(-1);  javascript:window.history.go(-1) 2.返回上一页并刷 ...

  8. vue封装tab切换

    vue封装tab切换 预览: 第一种 通过父传子标题,子传父事件 子组件 <template> <div class='app'> <div class="ta ...

  9. Python+Selenium(1)- 环境搭建

    一,Selenium 简介 Selenium是目前最流行的web自动化测试工具,也常用于网络爬虫,已经更新到3以上的版本. 1,组件 它提供了以下web自动化测试组件: Selenium IDE,Fi ...

  10. 《Clojure编程》笔记 第13章 测试

    目录 背景简述 第13章 测试 13.1 术语 13.2 clojure.test 13.2.1 定义测试的两种方式 13.2.1.1 用deftest宏把测试定义成单独的函数 13.2.1.2 用w ...