创建线程都有哪些方式?— Callable篇
今天我们来看一道面试题引发的思考
问: 创建线程都有哪些方式?
答: 我了解的有四种创建方式:
- 继承Thread类创建线程类
- 通过Runnable接口创建线程类
- 通过Callable和Future创建线程
- 通过线程池创建
相信大家回答这个问题没什么难度吧?通常问完创建方式,那么接下来就是问「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种功能:
- 能够中断执行中的任务;
- 判断任务是否执行完成;
- 获取任务执行完成后的结果。
但是我们必须明白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
博客园关注一下呗,博客园是我写文章的主阵地~
创建线程都有哪些方式?— Callable篇相关推荐
- 创建线程的几种方式---最全面的创建线程方式总结---线程创建方式面试看这篇就够了
前言:我相信创建线程的方式对于所有的java开发程序员来说都不陌生,在面试过程中这个问题也是高频考点.鉴于此,小编用本篇博文来整理几种线程的创建方式,希望对同学们有所帮助~ 文章目录 一.什么是进程? ...
- 创建线程的四种方式(Thread、Runnable、线程池、Callable)
目录 一.直接继承Thread类,然后重写run方法 二.实现Runnable接口 三.线程池创建 四.实现Callable接口 创建线程有四种方式:1.继承Thread类 2.实现Runnabl ...
- 创建线程的几种方式?JSP的九大内置对象及作用分别是什么?servlet的生命周期及常用方法?转发和重定向区别?ajax书写方式及内部主要参数都有哪些
文章目录 1 , 创建线程的几种方式? 问题扩展: 2 ,JSP的九大内置对象及作用分别是什么? (1) 问题分析: (2) 核心答案讲解: 1.request对象 2.response对象 3.se ...
- 【并发编程】创建线程的四种方式
上一篇我们初步认识了线程,现在我们来讲一下,创建线程的三种方式 1.继承Thread 类通过继承thread类,然后重写run方法(run方法中是线程真正执行的代码,runable也是如此)即可.当子 ...
- Java 并发 多线程:创建线程的四种方式
Java 并发 多线程: 创建线程的四种方式 继承 Thread 类并重写 run 方法 实现 Runnable 接口 实现 Callable 接口 使用线程池的方式创建 1. 通过继承 Thread ...
- java 创建线程_【80期】说出Java创建线程的三种方式及对比
点击上方"Java面试题精选",关注公众号 面试刷图,查缺补漏 >>号外:往期面试题,10篇为一个单位归置到本公众号菜单栏->面试题,有需要的欢迎翻阅. 一.Ja ...
- Java-JUC(六):创建线程的4种方式
Java创建线程的4种方式: Java使用Thread类代表线程,所有线程对象都必须是Thread类或者其子类的实例.Java可以用以下4种方式来创建线程: 1)继承Thread类创建线程: 2)实现 ...
- 创建线程的三种方式、线程运行原理、常见方法、线程状态
文章目录 1.创建线程的三种方式 1.1 继承Thread类并重写run方法 1.2 使用Runnable配合Thread 1.3 通过Callable和FutureTask创建线程 2.Runnab ...
- Java多线程 - Java创建线程的4种方式
文章目录 1. Java创建线程有哪几种方式? 1.1 线程创建方法一:继承Thread类创建线程类 1.2 线程创建方法二:实现Runnable接口创建线程目标类 1.5 线程创建方法三:使用Cal ...
最新文章
- Pytorch归一化方法讲解与实战:BatchNormalization、LayerNormalization、nn.BatchNorm1d和LayerNorm()和F.normalize()
- 8086CPU的出栈(pop)和入栈(push) 都是以字为单位进行的
- linux脚本石英钟,原生JS实现的简单小钟表功能示例
- 隧道野蛮模式_基于虚拟隧道的IPsec -华三 MSR26 路由器对接Juniper SSG
- SAP ABAP如何隐藏你写的程序代码(危险,请小心谨慎)
- C语言 realloc函数 带着内存游走的函数
- 深入.net平台的分层开发
- 设计模式那点事读书笔记(2)----抽象工厂模式
- 浅聊使用PHP实现微信公众号登录
- 编辑中的word变成只读_word只读模式怎么改 word保存文件提示此文件为只读无法保存修改方法...
- python读取fits第三方库_python读取fits文件
- proof-carrying data from accumulation schemes学习笔记
- IP地址、子网掩码、网络号、主机号、网络地址、主机地址
- 电脑卡顿?性能不足?一套连招榨干你的电脑!
- ORA-02019:未找到远程数据库的连接说明(数据库链接创建)
- win10电脑打不开我的电脑属性
- 文献综述怎么弄和书写格式
- VR资讯——局势明朗下的前景展望(V客学院知识分享)
- 阿里热修复集成,sophix加载本地补丁包
- Synology 搭建双机冗余High Availability服务
热门文章
- [html] 页面上的登录表单记住了密码(显示星号),但我又忘了密码,如何找回这个密码呢?
- 工作389-移动端控制
- 前端学习(2840):nevagator导航标签
- 前端学习(2338):记录解决问题的一次
- “约见”面试官系列之常见面试题之第六十一篇之IE和DOM事件流(建议收藏)
- spring mvc学习(40):restful的crud实现增加方式
- 第六十七期:Python爬虫44万条数据揭秘:如何成为网易音乐评论区的网红段子手
- 玩转oracle 11g(23):区分大小写和字符集不同
- SSL 多线程通信 linux openSSL C API编程
- HC-05蓝牙模块的配置和使用方法