Java多线程-Callable的Future返回值的使用
一般使用线程池执行任务都是调用的execute方法,这个方法定义在Executor接口中:
public interface Executor {
void execute(Runnable command);
}
这个方法是没有返回值的,而且只接受Runnable。
那么像得到线程的返回值怎嘛办呢?
在ExecutorService接口中能找到这个方法:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
这个方法接收两种参数,Callable和Runnable。返回值是Future。
下面具体看一下这些是什么东西。
Callable和Runnable
先看一下两个接口的定义:
Callable
public interface Callable<V> {
V call() throws Exception;
}
Runnable
interface Runnable {
public abstract void run();
}
和明显能看到区别:
1.Callable能接受一个泛型,然后在call方法中返回一个这个类型的值。而Runnable的run方法没有返回值
2.Callable的call方法可以抛出异常,而Runnable的run方法不会抛出异常。
Future
返回值Future也是一个接口,通过他可以获得任务执行的返回值。
定义如下:
public interface Future<V> {
boolean cancel(boolean var1); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long var1, TimeUnit var3) throws InterruptedException, ExecutionException, TimeoutException;
}
其中的get方法获取的就是返回值。
来个例子
submit(Callable task)
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(2);
//创建一个Callable,3秒后返回String类型
Callable myCallable = new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(3000);
System.out.println("calld方法执行了");
return "call方法返回值";
}
};
System.out.println("提交任务之前 "+getStringDate());
Future future = executor.submit(myCallable);
System.out.println("提交任务之后,获取结果之前 "+getStringDate());
System.out.println("获取返回值: "+future.get());
System.out.println("获取到结果之后 "+getStringDate());
}
public static String getStringDate() {
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
String dateString = formatter.format(currentTime);
return dateString;
}
}
通过executor.submit提交一个Callable,返回一个Future,然后通过这个Future的get方法取得返回值。
看一下输出:
提交任务之前 12:13:01
提交任务之后,获取结果之前 12:13:01
calld方法执行了
获取返回值: call方法返回值
获取到结果之后 12:13:04
get()方法的阻塞性
通过上面的输出可以看到,在调用submit提交任务之后,主线程本来是继续运行了。但是运行到future.get()的时候就阻塞住了,一直等到任务执行完毕,拿到了返回的返回值,主线程才会继续运行。
这里注意一下,他的阻塞性是因为调用get()方法时,任务还没有执行完,所以会一直等到任务完成,形成了阻塞。
任务是在调用submit方法时就开始执行了,如果在调用get()方法时,任务已经执行完毕,那么就不会造成阻塞。
下面在调用方法前先睡4秒,这时就能马上得到返回值。
System.out.println("提交任务之前 "+getStringDate());
Future future = executor.submit(myCallable);
System.out.println("提交任务之后 "+getStringDate());
Thread.sleep(4000);
System.out.println("已经睡了4秒,开始获取结果 "+getStringDate());
System.out.println("获取返回值: "+future.get());
System.out.println("获取到结果之后 "+getStringDate());
提交任务之前 12:36:04
提交任务之后 12:36:04
calld方法执行了
已经睡了4秒,开始获取结果 12:36:08
获取返回值: call方法返回值
获取到结果之后 12:36:08
可以看到吗,因为睡了4秒,任务已经执行完毕,所以get方法立马就得到了结果。
同样的原因,submit两个任务时,总阻塞时间是最长的那个。
例如,有两个任务,一个3秒,一个5秒。
Callable myCallable = new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(5000);
System.out.println("calld方法执行了");
return "call方法返回值";
}
};
Callable myCallable2 = new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(3000);
System.out.println("calld2方法执行了");
return "call2方法返回值";
}
};
System.out.println("提交任务之前 "+getStringDate());
Future future = executor.submit(myCallable);
Future future2 = executor.submit(myCallable2);
System.out.println("提交任务之后 "+getStringDate());
System.out.println("开始获取第一个返回值 "+getStringDate());
System.out.println("获取返回值: "+future.get());
System.out.println("获取第一个返回值结束,开始获取第二个返回值 "+getStringDate());
System.out.println("获取返回值2: "+future2.get());
System.out.println("获取第二个返回值结束 "+getStringDate());
输出
提交任务之前 14:14:47
提交任务之后 14:14:48
开始获取第一个返回值 14:14:48
calld2方法执行了
calld方法执行了
获取返回值: call方法返回值
获取第一个返回值结束,开始获取第二个返回值 14:14:53
获取返回值2: call2方法返回值
获取第二个返回值结束 14:14:53
获取第一个结果阻塞了5秒,所以获取第二个结果立马就得到了。
submit(Runnable task)
因为Runnable是没有返回值的,所以如果submit一个Runnable的话,get得到的为null:
Runnable myRunnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " run time: " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}; Future future = executor.submit(myRunnable);
System.out.println("获取的返回值: "+future.get());
输出为:
pool-1-thread-1 run time: 1493966762524
获取的返回值: null
submit(Runnable task, T result)
虽然submit传入Runnable不能直接返回内容,但是可以通过submit(Runnable task, T result)传入一个载体,通过这个载体获取返回值。这个其实不能算返回值了,是交给线程处理一下。
先新建一个载体类Data:
public static class Data {
String name;
String sex; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getSex() {
return sex;
} public void setSex(String sex) {
this.sex = sex;
}
}
然后在Runnable的构造方法中传入:
static class MyThread implements Runnable {
Data data; public MyThread(Data name) {
this.data = name;
} @Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("线程 执行:");
data.setName("新名字");
data.setSex("新性别");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
然后调用:
Data data = new Data();
Future<Data> future = executor.submit(new MyThread(data), data);
System.out.println("返回的结果 name: " + future.get().getName()+", sex: "+future.get().getSex());
System.out.println("原来的Data name: " + data.getName()+", sex: "+data.getSex());
输出:
线程 执行:
返回的结果 name: 新名字, sex: 新性别
原来的Data name: 新名字, sex: 新性别
发现原来的data也变了。
get(long var1, TimeUnit var3)
前面都是用的get()方法获取返回值,那么因为这个方法是阻塞的,有时需要等很久。所以有时候需要设置超时时间。
get(long var1, TimeUnit var3)这个方法就是设置等待时间的。
如下面的任务需要5秒才能返回结果:
Callable myCallable = new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(5000);
return "我是结果";
}
};
使用get:
Future future1 = executor.submit(myCallable);
System.out.println("开始拿结果 "+getStringDate());
System.out.println("返回的结果是: "+future1.get()+ " "+getStringDate());
System.out.println("结束拿结果 "+getStringDate());
输出是:
开始拿结果 16:00:43
返回的结果是: 我是结果 16:00:48
结束拿结果 16:00:48
现在要求最多等3秒,拿不到返回值就不要了,所以用get(long var1, TimeUnit var3)这个方法
方法的第一个参数是长整形数字,第二个参数是单位,跟线程池ThreadPoolExecutor的构造方法里一样的。
Future future1 = executor.submit(myCallable);
System.out.println("开始拿结果 "+getStringDate());
try {
System.out.println("返回的结果是: "+future1.get(3, TimeUnit.SECONDS)+ " "+getStringDate());
} catch (TimeoutException e) {
e.printStackTrace();
System.out.println("超时了 "+getStringDate());
}
System.out.println("结束拿结果 "+getStringDate());
然后输出是
过了三秒就抛出超时异常了,主线程继续运行,不会再继续阻塞。
异常
使用submit方法还有一个特点就是,他的异常可以在主线程中catch到。
而使用execute方法执行任务是捕捉不到异常的。
用下面这个Runnable来说,这个 里面一定会抛出一个异常
Runnable myRunnable = new Runnable() {
@Override
public void run() {
executor.execute(null);
}
};
使用execute
这里如果捕捉到异常,只打印一行异常信息。
try {
executor.execute(myRunnable);
} catch (Exception e) {
e.printStackTrace();
System.out.println("抓到异常 "+e.getMessage());
}
输出
并没有出现抓到异常哪行日志。而且这个异常输出是在线程pool-1-thread-1中,并不是在主线程中。说明主线程的catch不能捕捉到这个异常。
使用submit
try {
Future future1= executor.submit(myCallable);
future1.get();
} catch (Exception e) {
e.printStackTrace();
System.out.println("抓到异常 "+e.getMessage());
}
输出
这个就能抓到异常了。
Java多线程-Callable的Future返回值的使用的更多相关文章
- Java多线程 - Callable和Future
已知的创建多线程的方法有继承Tread类和实现Runnable方法.此外Java还提供了Callable接口,Callable接口也提供了一个call()方法来做为线程执行体.但是call()方法与r ...
- Java多线程Callable和Future类详解
public interface Callable<V> 返回结果并且可能抛出异常的任务.实现者定义了一个不带任何参数的叫做 call 的方法 public in ...
- java使用Callable创建又返回值的线程
并发编程使我们可以将程序分为很多个分离的,相互之间独立的任务,通过使用多线程的机制,将每个任务都会有一个执行线程来单独的驱动,一个线程是 进程中一个单一顺序控制流,一个进程可以拥有多个线程,也就相当于 ...
- Java多线程-新特性-有返回值的线程
在Java5之前,线程是没有返回值的,常常为了“有”返回值,破费周折,而且代码很不好写.或者干脆绕过这道坎,走别的路了. 现在Java终于有可返回值的任务(也可以叫做线程)了. 可返回值的任务必须实现 ...
- Java的Object.hashCode()的返回值到底是不是对象内存地址?
关于这个问题,查阅了网上的资料,发现证明过程太繁琐,这里我用了反证法. java.lang.Object.hashCode()的返回值到底是不是对象内存地址? hashCode契约 说到这个问题,大家 ...
- JAVA 多线程 Callable 与 FutureTask:有返回值的多线程
java多线程中,如果需要有返回值,就需要实现Callable接口. 看例子: 先建立一个Dowork这个类,就是平时某个业务的实现 package com.ming.thread.one; impo ...
- Callable+ThreadPoolExecutor实现多线程并发并获得返回值(转)
出处:https://blog.csdn.net/kity9420/article/details/80740466 前言 经常会遇到一些性能问题,比如调用某个接口,可能要循环调用100次,并且需要拿 ...
- Java Callable接口——有返回值的线程
实际开发过程中,我们常常需要等待一批线程都返回结果后,才能继续执行.<线程等待——CountDownLatch使用>中我们介绍了CountDownLatch的使用,通过使用CountDow ...
- Java - 多线程Callable、Executors、Future
http://blog.csdn.net/pipisorry/article/details/44341579 Introduction Callable接口代表一段能够调用并返回结果的代码; Fut ...
随机推荐
- python advanced programming ( I )
函数式编程 函数是Python内建支持的一种封装,通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计的基 ...
- python基础之列表list元组tuple
作者:tongqingliu 转载请注明出处:http://www.cnblogs.com/liutongqing/p/7041763.html python基础之列表list元组tuple 列表li ...
- eclipse环境搭建(插件安装)
转自:http://www.iteye.com/topic/982182 使用eclipse真的有年头了,相信java程序员没有不知道它的,最近在给团队中新来的应届生做指导,专门讲解了一下Eclips ...
- MV人物抹去
填充为背景 Music Video is one type of Video Processing.视频剪辑反向复原:视频图像分类:TensorFlow图像分类教程 改起个什么名字 视频分类数据集 ...
- STM32F10X-定时器/计数器
1.STM32F10X的计数器与定时器关系 当时钟连接外脉冲时是计数器:当时钟采用系统内部时钟时是定时器! 2.STM32F10X定时器的时钟源 STM32F10X定时器的时钟不是直接来至于APB1和 ...
- 5.form表单验证
自定义验证: 其他验证:
- 取得cxgrid的表格的值,仔细看下面的代码
procedure TfrmMain.cxGridDBTableView_List_PSSJCustomDrawCell(Sender: TcxCustomGridTableView; ACanvas ...
- Nginx 实现端口转发
https://www.cnblogs.com/zhaoyingjie/p/7248678.html Nginx 实现端口转发 什么是端口转发 当我们在服务器上搭建一个图书以及一个电影的应用,其中图书 ...
- 【转】利用线程更新ListView (2014-09-28 08:25:20)
http://blog.sina.com.cn/s/blog_44fa172f0102v2x0.html procedure TForm5.Button3Click(Sender: TObject); ...
- 深度学习主机环境配置: Ubuntu16.04+GeForce GTX 1080+TensorFlow
接上文<深度学习主机环境配置: Ubuntu16.04+Nvidia GTX 1080+CUDA8.0>,我们继续来安装 TensorFlow,使其支持GeForce GTX 1080显卡 ...