创建线程都有哪些方式?— Callable篇

今天我们来看一道面试题引发的思考

问: 创建线程都有哪些方式?

答: 我了解的有四种创建方式:

  1. 继承Thread类创建线程类
  2. 通过Runnable接口创建线程类
  3. 通过Callable和Future创建线程
  4. 通过线程池创建

相信大家回答这个问题没什么难度吧?通常问完创建方式,那么接下来就是问「1、2」跟「3」创建方式的不同了,只要说出「3」有返回值基本这个问题就过了,不管是出于好奇还是疑惑,我们今天来会会这个Callable。

实现Runnable接口或者是继承Thread,这两种方式都有个缺点,那就是在线程执行完之后无法获得返回值,所谓的返回值在这举个例子,比如有一个下载功能,下载某个界面的所有图片,图片下载完之后要返回给用户用于展示,如果我们采用方式1、2的话,显然很难实现,所以我们可以采用实现Callbale接口,并用Future接收多线程的执行结果来实现,具体代码如下:

public class TestCallable {public static void main(String[] args) {DownImgThread demo = new DownImgThread();FutureTask<List<String>> futureTask = new FutureTask<>(demo);new Thread(futureTask).start();try {System.out.println("-----开始获取图片-----");List<String> imgs = futureTask.get();for (String img: imgs) {System.out.println("图片:"+img);}System.out.println("-----获取图片结束-----");} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}/*** 下载图片的线程DownImgThread*/
public class DownImgThread implements Callable<List<String>> {@Overridepublic List<String> call() throws Exception {List result = new ArrayList();for (int i = 0; i < 10; i++) {result.add("https://sscai.club/img/a"+i+".jpg");}/*** 模拟3秒的网络请求操作*/Thread.sleep(3000);return result;}
}

执行结果:

-----开始获取图片-----
图片:https://sscai.club/img/a0.jpg
图片:https://sscai.club/img/a1.jpg
图片:https://sscai.club/img/a2.jpg
图片:https://sscai.club/img/a3.jpg
图片:https://sscai.club/img/a4.jpg
图片:https://sscai.club/img/a5.jpg
图片:https://sscai.club/img/a6.jpg
图片:https://sscai.club/img/a7.jpg
图片:https://sscai.club/img/a8.jpg
图片:https://sscai.club/img/a9.jpg
-----获取图片结束-----

通过如上代码我门来看看Callable和Future是如何使用的,首先DownImgThread类(下载图片类)实现了Callable接口,接口后面携带一个泛型T,这个泛型T将用作返回值,我们来看一下 Callable 的接口定义:

@FunctionalInterface
public interface Callable<V> {V call() throws Exception;
}

如上,Callable接口只有一个 call() 方法,同时 call() 方法有一个返回值 V ,所以上边我们在实现 call() 方法时才能将返回值返回回去。在 call() 方法内部通过一个「for循环+休眠」模拟了网络请求的过程,然后将图片的集合返回。

我们再回到主线程main方法,整体代码还是比较清晰的,可能小伙伴对FutureTask有点陌生。

在了解FutureTask之前,我们先来了解一下Future。

Future

Future是用来获取异步计算结果的,这个结果是未来的,只有当结果最终处理完成后才会出现在Future中,我们来看一下Future接口的源码。

public interface Future<V> {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}

解读这5个方法:

  • boolean cancel(boolean mayInterruptIfRunning):如果任务还没开始,执行cancel(…)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(…)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。
  • boolean isCancelled():如果任务完成前被取消,则返回true。
  • boolean isDone():如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
  • V get():获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
  • V get(long timeout, TimeUnit unit):获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。

总之,Future实际上提供了3种功能:

  1. 能够中断执行中的任务;
  2. 判断任务是否执行完成;
  3. 获取任务执行完成后的结果。

但是我们必须明白Future只是一个接口,我们无法直接创建对象,因此就需要其实现类FutureTask。

FutureTask

用FutureTask来实现Future,但是这里并不是直接实现的,我们通过一张图来看一下之间的关系:

FutureTask代码中对应截图部分:

public class FutureTask<V> implements RunnableFuture<V> {  

FutureTask实现了RunnableFuture接口,然后我们再来看一下RunnableFuture接口:

public interface RunnableFuture<V> extends Runnable, Future<V> {  void run();  
}  

RunnableFuture同时实现了Runnable、Future这两个接口,也就是,既可以通过Runnable接口实现线程,也可以通过Fufure取得异步线程执行后的结果,因为实现了Runnable的缘故,那么FutureTask也可以直接提交给Executor执行,Executor接口代码如下:

public interface Executor {void execute(Runnable command);
}

这个地方为什么要提到Executor呢?

我们再回到最上边写的那个测试代码TestCallable中,有这么一行代码:new Thread(futureTask).start();,这段代码是用来创建并开启一个线程,但是问题来了,这种创建线程的方式是有很大弊端的:

  • 每次new Thread新建对象性能差;
  • 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或OOM;
  • 缺乏更多功能,如定时执行、定期执行、线程中断。

而通过Executor线程池的方式显然要效率很高。

至此,我们了解了Callable、Future、FutureTask,同时知道了Future跟FutureTask之间的关系,还间接地了解到FutureTask也可以直接提交给Executor执行,那么我们重新修改一下最开始的代码:

public class TestCallable {public static void main(String[] args) {/**创建Callable对象任务 **/DownImgThread demo = new DownImgThread();/**创建线程池**/ExecutorService executorService = Executors.newSingleThreadExecutor();/**提交任务并获取执行结果**/Future<List<String>> futureTask = executorService.submit(demo);/**关闭线程池**/executorService.shutdown();try {System.out.println("-----开始获取图片-----");List<String> imgs = futureTask.get();for (String img: imgs) {System.out.println("图片:"+img);}System.out.println("-----获取图片结束-----");} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}

执行结果:

-----开始获取图片-----
图片:https://sscai.club/img/a0.jpg
图片:https://sscai.club/img/a1.jpg
图片:https://sscai.club/img/a2.jpg
图片:https://sscai.club/img/a3.jpg
图片:https://sscai.club/img/a4.jpg
图片:https://sscai.club/img/a5.jpg
图片:https://sscai.club/img/a6.jpg
图片:https://sscai.club/img/a7.jpg
图片:https://sscai.club/img/a8.jpg
图片:https://sscai.club/img/a9.jpg
-----获取图片结束-----

眼尖的小伙伴,估计要开始吐槽创建线程池的方式了,是的阿里巴巴开发规范中强制要求「线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式」,所以也是给小伙伴留个作业,自己动手修改一下上方代码吧。

本文首发于博客园:https://www.cnblogs.com/niceyoo/p/13380070.html

博客园关注一下呗,博客园是我写文章的主阵地~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/413953.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[js] 说说你对JSBridge的理解

[js] 说说你对JSBridge的理解 js和原生应用之间交互的桥梁个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题

ASP.NET Core 网站发布到Linux服务器

长期以来&#xff0c;使用.NET开发的应用只能运行在Windows平台上面&#xff0c;而目前国内蓬勃发展的互联网公司由于成本的考虑&#xff0c;大量使用免费的Linux平台&#xff0c;这就使得.NET空有一身绝技但无法得到广大的施展空间&#xff0c;.NET平台被认为只适合开发企业内…

如何暂停一个正在运行的线程?

今天把小伙伴问懵了&#xff0c;小刚&#xff0c;你知道怎么停止一个线程吗&#xff1f; 这…&#xff0c;这…&#xff0c;stop&#xff1f; 原来平时小刚这小子只知道创建线程&#xff0c;不知道怎么暂停线程呀~[狗头] 停止线程是在多线程开发中很重要的技术点&#xff0c;…

[js] js循环中调用异步的方法,如何确保执行结果的顺序是正确的?

[js] js循环中调用异步的方法&#xff0c;如何确保执行结果的顺序是正确的&#xff1f; An example:let searchApi function(arg){return new Promise((resolve,reject)>{setTimeout(()>{console.log(arg)resolve(arg)}, 20)}) }; let fields [1,2,3,4]; let arr new…

Page Cache的落地问题

除非特别说明&#xff0c;否则本文提到的写操作都是 buffer write/write back。 起因 前几天讨论到一个问题&#xff1a;Linux 下文件 close成功&#xff0c;会不会触发 “刷盘”&#xff1f; 其实这个问题根本不用讨论&#xff0c;查一下就知道。 man 2 close 的 NOTES 一节里…

Docker中搭建FastDFS文件系统(多图)

关于FastDFS FastDFS 是以 C 语言开发的一项开源轻量级分布式文件系统&#xff0c;他对文件进行管理&#xff0c;主要功能有&#xff1a;文件存储&#xff0c;文件同步&#xff0c;文件访问&#xff08;文件上传/下载&#xff09;等&#xff0c;特别适合以文件为载体的在线服务…

[js] 写一个方法遍历指定对象的所有属性

[js] 写一个方法遍历指定对象的所有属性 Object.keys()、Object.values()只能遍历对象自有的属性&#xff0c;for in 可以遍历原型中的属性。个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 …

jquery(2)

获得内容 - text()、html() 以及 val() 三个简单实用的用于 DOM 操作的 jQuery 方法&#xff1a;text() - 设置或返回所选元素的文本内容 ----不能识别标签 html() - 设置或返回所选元素的内容&#xff08;包括 HTML 标记&#xff09; val() - 设置或返回表单字段的值----- 即登…

[js] 实现一个函数记忆的方法

[js] 实现一个函数记忆的方法 同步运算结果缓存&#xff0c;这个就老生常谈了&#xff1a; function useCache(func) {var cache {};return function() {var key arguments.length Array.prototype.join.call(arguments);if(cache[key]) return cache[key];cache[key] fu…

SpringBoot集成FastDFS依赖实现文件上传

前言 对FastDFS文件系统安装后的使用。 FastDFS的安装请参考这篇&#xff1a;https://www.cnblogs.com/niceyoo/p/13511082.html 本文环境&#xff1a;IDEA JDK1.8 Maven 1、引入依赖 简单说一下这个依赖部分&#xff0c;目前大部分都是采用的如下依赖&#xff1a; <…

SQL取最大值编码(自动编码)

SQL取最大值编码(自动编码) 用途 : 使用SQL语法做出自动编码效果&#xff0c;例如将单号自动1后&#xff0c;产生该笔单号 Table说明 SQL语法 SELECT AREPLICATE(0,7-len(convert(varchar,((MAX(right(ae002,7)))1))))(convert(varchar,((MAX(right(ae002,7)))1))) from ygmae …

[js] 手写一个trim()的方法

[js] 手写一个trim()的方法 function trim(str) { if (str[0] && str[str.length - 1] ) { return trim(str.substring(1, str.length - 1)) } else if (str[0] ! && str[str.length - 1] ) { return trim(str.substring(0, str.length - 1)) } else…

微信小程序里如何使用npm?小程序集成友盟举例

1、执行npm初始化指令 小程序根目录&#xff0c;命令执行如下指令&#xff1a; npm init执行后会让加载项目初始信息&#xff0c;具体截图如下&#xff1a; 2、执行安装npm包指令 在这我们举个例子&#xff0c;以接入友盟统计SDK为例&#xff0c;执行命令如下&#xff1a;…

Spring MVC 5 + Thymeleaf 基于Java配置和注解配置

Spring MVC 5 Thymeleaf 注解配置 Spring的配置方式一般为两种&#xff1a;XML配置和注解配置 Spring从3.0开始以后&#xff0c;推荐使用注解配置&#xff0c;这两种配置的优缺点说的人很多&#xff0c;我就不说了&#xff0c;自行体会&#xff0c;下面就用注解配置实现一个Sp…

[js] 你是如何比较js函数的执行速度的?

[js] 你是如何比较js函数的执行速度的&#xff1f; 采用chrome performance apiconsole.time(flag); console.timeEnd(flag);performance api比较精准的 console.time(flag); 也行 搭配timelog 多次测量个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容…

docker镜像无法删除 Error:No such image:xxxxxx

1、前言 docker镜像无法删除&#xff0c;通过 docker images 查看镜像明明存在就是删除不了。 删除提示&#xff1a;Error&#xff1a;No such image&#xff1a;xxxxxxx 具体截图内容如下&#xff1a; 2、解决方法 进入目录&#xff1a; cd /var/lib/docker/image/over…

[js] axios拦截器原理是什么?

[js] axios拦截器原理是什么&#xff1f; 拦截器原理其实就是用use添加用户自定义的函数到拦截器的数组中。 最后把他们放在拦截器请求前&#xff0c;请求后。组成promise链式调用。 // 组成Promise链// Hook up interceptors middleware// 把 xhr 请求 的 dispatchRequest 和…

python中集合set,字典dict和列表list的区别以及用法

python中set代表集合&#xff0c;list代表列表,dict代表字典 set和dict的区别在于&#xff0c;dict是存储key-value&#xff0c;每一个key都是唯一的&#xff0c;set相对于dict存储的是key&#xff0c;且key是唯一的,list除了变量外都可以存储 dict{"a":1,"b&qu…

[js] fetch和axios请求的原理都是基于XMLHttpRerequst吗?

[js] fetch和axios请求的原理都是基于XMLHttpRerequst吗&#xff1f; axios是基于XMLHttpRequest的封装&#xff0c;而fetch是js原生支持的网络请求api&#xff0c;这在浏览器底层进行了支持。个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#x…

LeetCode(81): 搜索旋转排序数组 II

Medium&#xff01; 题目描述&#xff1a; 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 ( 例如&#xff0c;数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。 编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true&#xff0c;否则返回 false。 示…