Java开发笔记(九十八)利用Callable启动线程
前面介绍了如何利用Runnable接口构建线程任务,该方式确实方便了线程代码的复用与共享,然而Runnable不像公共方法那样有返回值,也就无法将线程代码的处理结果传给外部,造成外部既不知晓该线程是否已经执行完毕,也不了解该线程的运算结果是什么,总之无法跟踪分线程的行动踪迹。这里显然是不完美的,调用方法都有返回值,为何通过Runnable启动线程就无法获得返回值呢?为此Java又提供了另一种开启线程的方式,即利用Callable接口构建任务代码,实现该接口需要重写call方法,call方法类似run方法,同样存放着开发者定义的任务代码。不同的是,run方法没有返回值,而call方法支持定义输出参数,从而在设计上保留了分线程在运行结束时返回结果的可能性。
Callable是个泛型接口,它的返回值类型在外部调用时指定,要想创建一个Callable实例,既能通过定义完整的新类来实现,也能通过普通的匿名内部类实现。举个简单的应用例子,比如希望开启分线程生成某个随机数,并将随机数返回给主线程,则采取匿名内部类方式书写的Callable定义代码如下所示:
// 定义一个Callable代码段,返回100以内的随机整数
// 第一种方式:采取匿名内部类方式书写
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() { // 返回值为Integer类型
int random = new Random().nextInt(100); // 获取100以内的随机整数
// 以下打印随机数日志,包括当前时间、当前线程、随机数值等信息
PrintUtils.print(Thread.currentThread().getName(), "任务生成的随机数="+random);
return random;
}
};
由于Callable又是个函数式接口,因此能够利用Lambda表达式来简化匿名内部类,于是掐头去尾后的Lambda表达式代码示例如下:
// 第二种方式:采取Lambda表达式书写
Callable<Integer> callable = () -> {
int random = new Random().nextInt(100);
PrintUtils.print(Thread.currentThread().getName(), "任务生成的随机数="+random);
return random;
};
因为获取随机数的关键代码仅有一行,所以完全可以进一步精简Lambda表达式的代码,压缩冗余后只有下面短小精悍的一行代码:
// 第三种方式:进一步精简后的Lambda表达式
Callable<Integer> callable = () -> new Random().nextInt(100);
有了Callable实例之后,还需要引入未来任务FutureTask把它包装一下,因为只有FutureTask才能真正跟踪任务的执行状态。以下是FutureTask的主要方法说明:
run:启动未来任务。
get:获取未来任务的执行结果。
isDone:判断未来任务是否执行完毕。
cancel:取消未来任务。
isCancelled:判断未来任务是否取消。
现在结合Callable与FutureTask,串起来运行一下拥有返回值的未来任务,首先把Callable实例填进FutureTask的构造方法,由此得到一个未来任务的实例;接着调用未来任务的run方法启动该任务,最后调用未来任务的get方法获取任务的执行结果。根据以上步骤编写的未来任务代码如下所示:
// 根据代码段实例创建一个未来任务
FutureTask<Integer> future = new FutureTask<Integer>(callable);
future.run(); // 运行未来任务
try {
Integer result = future.get(); // 获取未来任务的执行结果
PrintUtils.print(Thread.currentThread().getName(), "主线程的执行结果="+result);
} catch (InterruptedException | ExecutionException e) {
// get方法会一直等到未来任务的执行完成,由于等待期间可能收到中断信号,因此这里得捕捉中断异常
e.printStackTrace();
}
运行上述的任务调用代码,观察到的任务日志如下:
16:48:53.363 main 任务生成的随机数=11
16:48:53.422 main 主线程的执行结果=11
有没有发现什么不对劲的地方?第一行日志是在Callable实例的call方法中打印的,第二行日志是在主线程获得返回值后打印的,可是从日志看,两行日志都由main线程也就是主线程输出的,说明未来任务仍然由主线程执行,而非由分线程执行。
那么如何才能开启分线程来执行未来任务呢?当然还得让Thread类亲自出马了,就像使用分线程执行Runnable任务那样,同样要把Callable实例放入Thread的构造方法当中,然后再调用线程实例的start方法方可启动线程任务。于是添加线程类之后的未来任务代码变成了下面这样:
// 根据代码段实例创建一个未来任务
FutureTask<Integer> future = new FutureTask<Integer>(callable);
// 把未来任务放入新创建的线程中,并启动分线程处理
new Thread(future).start();
try {
Integer result = future.get(); // 获取未来任务的执行结果
PrintUtils.print(Thread.currentThread().getName(), "主线程的执行结果="+result);
} catch (InterruptedException | ExecutionException e) {
// get方法会一直等到未来任务的执行完成,由于等待期间可能收到中断信号,因此这里得捕捉中断异常
e.printStackTrace();
}
运行上面的未来任务代码,观察到以下的程序日志:
16:49:49.816 Thread-0 任务生成的随机数=38
16:49:49.820 main 主线程的执行结果=38
从日志中的Thread-0名称可知,此时的未来任务总算交由分线程执行了。
更多Java技术文章参见《Java开发笔记(序)章节目录》
Java开发笔记(九十八)利用Callable启动线程的更多相关文章
- Java开发笔记(八十三)利用注解技术检查空指针
注解属于比较高级的Java开发技术,前面介绍的内置注解专用于编译器检查代码,另外一些注解则由各大框架定义与调用,像Web开发常见的Spring框架.Mybatis框架,Android开发常见的Butt ...
- Java开发笔记(八十)利用反射技术操作私有方法
前面介绍了如何利用反射技术读写私有属性,不单是私有属性,就连私有方法也能通过反射技术来调用.为了演示反射的逆天功能,首先给Chicken鸡类增加下列几个私有方法,简单起见弄来了set***/get** ...
- Java开发笔记(八十九)缓存字节I/O流
文件输出流FileOutputStream跟FileWriter同样有个毛病,每次调用write方法都会直接写到磁盘,使得频繁的写操作性能极其低下.正如FileWriter搭上了缓存兄弟Buffere ...
- Java开发笔记(八十八)文件字节I/O流
前面介绍了如何使用字符流读写文件,并指出字符流工具的处理局限,进而给出随机文件工具加以改进.随机文件工具除了支持访问文件内部的任意位置,更关键的一点是通过字节数组读写文件数据,采取字节方式比起字符方式 ...
- Java开发笔记(八十六)通过缓冲区读写文件
前面介绍了利用文件写入器和文件读取器来读写文件,因为FileWriter与FileReader读写的数据以字符为单位,所以这种读写文件的方式被称作“字符流I/O”,其中字母I代表输入Input,字母O ...
- Java开发笔记(八十四)文件与目录的管理
程序除了处理内存中的数据结构,还要操作磁盘上的各类文件,这里的磁盘是个统称,泛指可以持久保留数据的存储介质,包括但不限于:插在软驱中的软盘.固定在机箱中的硬盘.插在光驱中的光盘.插在USB接口上的U盘 ...
- Java开发笔记(八十七)随机访问文件的读写
前面介绍了字符流读写文件的两种方式,包括文件字符流和缓存字符流,但是它们的写操作都存在一个问题:不管是write方法还是append方法,都只能从文件开头写入,而不能追加到文件末尾或者在文件中间某个位 ...
- Java开发笔记(八十五)通过字符流读写文件
前面介绍了文件的信息获取.管理操作,以及目录下的文件遍历,那么文件内部数据又是怎样读写的呢?这正是本文所要阐述的内容.File工具固然强大,但它并不能直接读写文件,而要借助于其它工具方能开展读写操作. ...
- Java开发笔记(八十二)注解的基本单元——元注解
Java的注解非但是一种标记,还是一种特殊的类型,并且拥有专门的类型定义.前面介绍的五种内置注解,都可以找到对应的类型定义代码,例如查看注解@Override的源码,发现它的代码定义是下面这样的: @ ...
随机推荐
- vue.js 使用高德地图
1.获取key值 注册成为高德开发者需要分三步: 第一步,注册高德开发者: 第二步,去控制台创建应用: 第三步,获取Key 2.修改配置文件 webpack.base.conf.js externa ...
- ES6之箭头函数中的this
在讲箭头函数中的this之前我们先介绍一下普通函数中的this. 普通函数中的this: (1)this指向它的直接调用者 (2)默认的,非严格模式下,没找到直接调用者则指向window ( ...
- ANDROID开发中资源文件和资源ID是如何映射的
http://tweetyf.org/2013/02/mapping_between_res_resid_android.html
- pig 入门教程(1)
出处:http://www.codelast.com/ 本文可以让刚接触pig的人对一些基础概念有个初步的了解. 本文大概是互联网上第一篇公开发表的且涵盖大量实际例子的Apache Pig中文教程(由 ...
- ssh远程转发使远程主机在所有ip上监听
起因:突然一夜之间电信扰拨号ip全变内网地址了,这样即使用了动态域名,绑定的也不是本机ip,外部无法访问了.虽然打电话找电信反映说是可以改回来,但必须先解决眼前的问题,访问内网服务器上的svn仓库. ...
- Python开发【第三篇】:分支循环
1. if 条件语句 语法: if 条件: 代码块 # 条件为真执行 else: # else 可选 代码块 # 条件为假执行 示例: n = int(input('请输入一个数字:')) i ...
- Open-source Tutorial - Material Design for WPF UI
安装 Material Design Themes 通过 NuGet 包管理器搜索自动安装 通过 NuGet 包管理器控制台手动安装 Install-Package MaterialDesignThe ...
- EasyUI 在mvc中的引入与使用
使用步骤: 一.先引入: 1.easyui下载,可以去官方网站去下载最新版EasyUI官方下载地址:http://www.jeasyui.com/download/index.php 2.下载后解压, ...
- python接口自动化(三十六)-封装与调用--流程类接口关联续集(详解)
简介 上一篇已经给大家都介绍过了流程类接口关联,但是由于博客的登录机制改变,所以没有办法给小伙伴们实战演练一下,那么这篇就按照上一篇计划的用jenkins来给小伙伴们演示一下流程类接口的封装和调用,其 ...
- HDP3.1 中配置 YARN 的 timeline server 使用外部的 HBase
HDP3.1 中的 YARN 的 timeline server 默认使用内置的 HBase,不知道为什么,总是过几天就挂掉,暂时还没找到原因.后来想着让它使用外部的 HBase 看看会不会还有此问题 ...