【并发编程】 --- Runnable、Callable、Future和FutureTask之间的关系

Fern ·
更新时间:2024-09-21
· 970 次阅读

文章目录1 创建一个线程的方式到底有几种???2 如何使用FutureTask 、Future、Callable、线程池实现线程2.1 FutureTask + Callable实现多线程2.2 线程池+Future+Callable 实现多线程3 Runnable、Callable、Future和FutureTask之间的关系3.1 整体关系介绍3.2 简单看一下源码3.3 四者关系小结

源码地址:https://github.com/nieandsun/concurrent-study.git

1 创建一个线程的方式到底有几种???

在java中其实创建一个线程的方式本质上只有两种:

(1)继承Thread类 (2)实现Runnable接口

这个观点可以通过Thread类中的注释进行证明:
在这里插入图片描述
但是同时相信很多人也都知道,使用下面两种组合,也可以创建一个线程:

(1)FutureTask + Callable (2)线程池+Future+Callable

这时我想有些人可能会懵逼,难道JDK源码中的注释还有问题??? —> 其实并不是。

2 如何使用FutureTask 、Future、Callable、线程池实现线程 2.1 FutureTask + Callable实现多线程 code package com.nrsc.ch2.future; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; @Slf4j public class FutureTaskDemo1 { static class MyCallable implements Callable { @Override public Integer call() throws Exception { int num = 0; log.info("开始进行计算。。。"); for (int i = 0; i < 5; i++) { Thread.sleep(1000); num++; } return num; } } public static void main(String[] args) throws Exception { FutureTask futureTask = new FutureTask(new MyCallable()); new Thread(futureTask).start(); //-----// Thread.sleep(1000); log.info("do something in main"); //在Callable中的方法运行完之前 这里会一直阻塞,直到Callable运行完 //然后就可以从future里获取到结果了 Integer result = futureTask.get(); log.info("result: {}", result); } } 运行结果

在这里插入图片描述

2.2 线程池+Future+Callable 实现多线程 code package com.nrsc.ch2.future; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @Slf4j public class FutureDemo1 { static class MyCallable implements Callable { @Override public Integer call() throws Exception { int num = 0; log.info("开始进行计算。。。"); for (int i = 0; i < 5; i++) { Thread.sleep(1000); num++; } return num; } } public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); //线程池通过submit开启线程 Future future = executorService.submit(new MyCallable()); Thread.sleep(1000); log.info("do something in main"); //在Callable中的方法运行完之前 这里会一直阻塞,直到Callable运行完 //然后就可以从future里获取到结果了 Integer result = future.get(); log.info("result: {}", result); executorService.shutdown(); } } 运行结果,和2.1基本一致:

在这里插入图片描述

3 Runnable、Callable、Future和FutureTask之间的关系 3.1 整体关系介绍

看过2中的两个栗子,我想你肯定也已经知道揭开这几个对象之间关系的突破口就是FutureTask
为什么这么说呢? 首先我们应该知道Thread的构造函数就下面这么几个。并且在1中也已经说过,Java中开启线程的方式要么是继承Thread类,要么是使用继承了Runnable接口的类。但是在2.1中我却在创建Thread类时传入了一个FutureTask ----》 那这说明这个FutureTask 肯定就是一个Runnable(多态)。
在这里插入图片描述
接下来我们来看一下FutureTask类的继承关系图:
在这里插入图片描述
由此可知FutureTask确实是Runnable接口的子类,同时它还实现了Future接口。

3.2 简单看一下源码 Runnable接口源码: @FunctionalInterface public interface Runnable { public abstract void run(); } RunnableFuture接口源码: public interface RunnableFuture extends Runnable, Future { void run(); } Future接口: public interface Future { //尝试取消线程 boolean cancel(boolean mayInterruptIfRunning); //判断线程任务是否已经被取消 boolean isCancelled(); //判断线程中的任务是否执行完 boolean isDone(); //获取执行任务的结果 ---》 在任务执行完之前一直阻塞 V get() throws InterruptedException, ExecutionException; //在指定时间内尝试获取执行任务的结果 V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; } Callable的源码: @FunctionalInterface public interface Callable { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }

接着再来看一下FutureTask中的方法
在这里插入图片描述

3.3 四者关系小结

通过前面的内容可知:

(1)JDK允许的创建线程的方式只有两种,而这两种方式都无法获取线程中任务运行结果的返回值 (2)为了想办法获取到各个线程的执行结果,在没办法改变创建线程方式的前提下 (2.1)出现了FutureTask,一种特殊的Runnable (2.2)我们的线程开启后其实还是会走没有返回值的run()方法 —》 其实就是实现Runnable接口开启线程的那种方式 (2.3)但是在没有返回值的run()方法内部,可以调用因实现了Callable接口而重写的有返回值的call()方法 (2.4)在call()方法调用完之后,就可以获取到该线程的执行结果了 (3)Future的作用之一就是拿到线程的执行结果。

画个图的话,可以这样表示:
在这里插入图片描述

介绍到这里之后,有兴趣的可以看一看为什么使用线程池,传入一个Callable,返回的是一个Future。。。相信你肯定能看出个123来☺☺☺

end


作者:nrsc



future 并发编程 并发 关系

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章