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的源码,发现它的代码定义是下面这样的: @ ...
随机推荐
- 在Oracle中设置主键自增
转自:https://www.2cto.com/database/201705/636725.html 数据库设置主键自增">oracle数据库设置主键自增: --创建表 create ...
- 开启struts2自带的开发模式常量
在以前的开发中,当修改一些配置时总是不能及时地更新到服务器,我们总会重新部署或重启来更新改变的内容,在struts2中可以通过一个常量来达到此目的.即在struts.xml中的<struts&g ...
- hive 连接查询sql对比效率
准备4个表 从mysql 导出excel 转换为txt 创建hive 表的导入文件 create table bdqn_student( sno int, sname string, sbirthda ...
- 将gridview 的数据导出EXCEL
gridview数据 单击“导出EXCEL”按钮后 1.在上面的代码中,先将gridview绑定到指定的数据源中,然后在button按钮(用来做导出到EXCEL的)的事件中,写入相关的代 ...
- C#笔记(二)
转换操作符:操作符重载,可自定义实现从一种类型到另一种类型的显示或者隐式转换 : true/false也可进行操作符重载: LINQ中大部分查询运算符都有一个非常重要的特性:延迟执行.这意味着,他们不 ...
- 7、linux常见系统环境变量
使用env命令显示所有环境变量 env (常见的有HOSTNAME,SHELL,HISTSIZE,PERL5LIB,USER,PATH,PWD,LANG,HOME, LD_LIBRARY_PATH ...
- 第五课5、ROS客户端2
1.简单的主题(topic)发布者和主题订阅者: 编写主题发布者节点需要: a.初始化ROS系统: b.广播消息:在foo主题上发布Foo_type_msg类型的消息 c.指定频率发布消息到foo主题 ...
- ant安装和配置
一.安装ant到官方主页http://ant.apache.org下载新版(我用的1.9.2)的ant,得到的是一个apache-ant-1.9.2-bin.zip的压缩包.将其解压到你的硬盘上,例如 ...
- bzoj 3123: [Sdoi2013]森林(45分暴力)
3123: [Sdoi2013]森林 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 4184 Solved: 1235[Submit][Status ...
- [Xcode 实际操作]四、常用控件-(4)UILabel文本标签的自动换行
目录:[Swift]Xcode实际操作 本文将演示标签控件的换行功能, 在项目导航区,打开视图控制器的代码文件[ViewController.swift] import UIKit class Vie ...