创建线程都有哪些方式?— 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,一经查实,立即删除!

相关文章

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

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

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

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

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

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

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 …

微信小程序里如何使用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…

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…

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…

第6课 仿Siri机器人-语音朗读和语音识别

一、功能设计输入文本&#xff0c;单击“朗读”按钮&#xff0c;由手机读出该文本&#xff08;如果没有输入文本&#xff0c;则弹出消息框警告“请输入文本&#xff09;&#xff1b;单击“识别”按钮&#xff0c;读入语音&#xff0c;从文本框中输出文字。&#xff08;另&#…

口述完SpringMVC执行流程,面试官就让同事回家等消息了

Srping MVC 执行流程真的是老生常谈的话题了&#xff0c;最近同事小刚出去面试&#xff0c;前面面试官相继问了几个 Spring 相关的问题&#xff0c;但当面试官问他&#xff0c;你知道 Srping MVC 的执行流程吗&#xff1f;小刚娴熟的巴拉巴拉回答完后&#xff0c;面试官就让他回…

C++ 判断系统大小字节序

bool IsLitterEndian() {union UTest{std::uint16_t t;std::uint8_t c;} endianTest{ 0x01 };return (endianTest.c 0x01); } 转载于:https://www.cnblogs.com/fluteary/p/9178627.html

macos brew zookeeper,安装后zookeeper启动失败?

一、Zookeeper安装流程 执行如下安装命令&#xff1a; brew install zookeeper执行截图如下&#xff1a; 安装后查看 zookeeper 安装信息&#xff08;默认拉取最新版本&#xff09; brew info zookeeper执行截图如下&#xff1a; 二、Zookeeper启动、状态查询、及关闭 启…

为什么SimpleDateFormat不是线程安全的?

一、前言 日期的转换与格式化在项目中应该是比较常用的了&#xff0c;最近同事小刚出去面试实在是没想到被 SimpleDateFormat 给摆了一道… 面试官&#xff1a;项目中的日期转换怎么用的&#xff1f;SimpleDateFormat 用过吗&#xff1f;能说一下 SimpleDateFormat 线程安全问…

【Python 学习_第2周_程序代码】金角大王培训第二周练习_购物车代码,将写的代码和老师代码比较,记录下收获...

培训第二周&#xff0c;课堂练习为编写一段购物车代码&#xff0c;需求描述如下&#xff1a; 1.提示用户输入薪水 2.用户输入薪水后&#xff0c;打印商品编号、内容及价格 3.提醒用户输入商品代码&#xff0c;若余额大于等于商品价格&#xff0c;可购买&#xff1b;若小于&…

ActiveMQ Cannot send, channel has already failed: tcp:127.0.0.1:8161

仅针对如下错误内容&#xff1a; Cannot send, channel has already failed: tcp://127.0.0.1:8161一种尝试解决&#xff0c;修改连接端口为 61616&#xff1a; tcp://127.0.0.1:61616在没有修改过 ActiveMQ 配置文件情况下&#xff0c;默认 tcp 端口为 61616&#xff0c;htt…

pip安装报错处理+PyPi源切换教程

一、pip安装出错类型 1.1 pip版本过旧导致不能安装 报错提示&#xff1a; You are using pip version 9.0.3, however version 10.0.1 is available. You should consider upgrading via the python -m pip install --upgrade pip comm and. 可通过以下命令升级pip python -m p…

面试官:说一下List排序方法

1. 前言 排序算是比较高频的面试题了&#xff0c;节前面试了的两家公司都有问到排序问题&#xff0c;整理后分享给大家&#xff08;文末见总结&#xff09;。 通常我们想到实现排序就是 Collections 工具类的 sort() 方法&#xff0c;而 sort() 方法有两种&#xff1a; 直接调…

python之路——内置函数和匿名函数

楔子 在讲新知识之前&#xff0c;我们先来复习复习函数的基础知识。 问&#xff1a;函数怎么调用&#xff1f; 函数名() 如果你们这么说。。。那你们就对了&#xff01;好了记住这个事儿别给忘记了&#xff0c;咱们继续谈下一话题。。。 来你们在自己的环境里打印一下自己的名字…

SpringBoot打包成Docker镜像

1. 本文环境 Maven&#xff1a;3.6.3 &#xff08;Maven配置参考&#xff09; SpringBoot version&#xff1a;2.3.4.RELEASE Docker version&#xff1a; 19.03.11 &#xff08;Docker搭建参考&#xff09; JDK version&#xff1a;1.8.0_221 &#xff08;JDK搭建参考&…