并发编程 05—— Callable和Future
Java并发编程实践 目录
并发编程 04—— 闭锁CountDownLatch 与 栅栏CyclicBarrier
并发编程 06—— CompletionService : Executor 和 BlockingQueue
并发编程 10—— 任务取消 之 关闭 ExecutorService
并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
并发编程 13—— 线程池的使用 之 配置ThreadPoolExecutor 和 饱和策略
并发编程 20—— AbstractQueuedSynchronizer 深入分析
概述
第4部分 实例——JAVA并行异步编程线程池+FutureTask
Callable 和 Future 是比较有趣的一对组合。当我们需要获取线程的执行结果时,就需要用到它们。Callable用于产生结果,Future用于获取结果。
第1部分 Callable
Callable 是一个接口,它只包含一个call()方法。Callable是一个返回结果并且可能抛出异常的任务。
为了便于理解,我们可以将Callable比作一个Runnable接口,而Callable的call()方法则类似于Runnable的run()方法。
Callable的源码如下:
public interface Callable<V> {
V call() throws Exception;
}
Callable 接口类似于 Runnable
,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
Executors
类包含一些从其他普通形式转换成 Callable 类的实用方法。
第2部分 Future
Future 是一个接口。它用于表示异步计算的结果。提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
Future的源码如下:
boolean cancel(boolean mayInterruptIfRunning)
试图取消对此任务的执行。
V get()
如有必要,等待计算完成,然后获取其结果。
V get(long timeout, TimeUnit unit)
如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
boolean isCancelled()
如果在任务正常完成前将其取消,则返回 true。
boolean isDone()
如果任务已完成,则返回 true。
在讲解FutureTask之前,先看看Callable, Future, FutureTask它们之间的关系图,如下:
Future表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等。在Future规范中包含的隐含意义是,任务的生命周期只能前进,不能后退,就像ExecutorService的生命周期一样。当某个任务完成以后,它就永远停留在“完成”状态上。
get方法的行为取决于任务的状态(尚未开始、正在运行、已完成)。如果任务已经完成,那么get会立即返回或者抛出一个Exception,如果任务没有完成,那么get将阻塞并直到任务完成。如果任务抛出了异常,那么get将该异常封装为ExecutionException并重新抛出。如果任务被取消,那么get将抛出CancellationException。如果get抛出了ExecutionException,那么可以通过getCause来获得封装的初始异常。
异常抛出:
CancellationException - 如果计算被取消
ExecutionException - 如果计算抛出异常
InterruptedException - 如果当前的线程在等待时被中断
TimeoutException - 如果等待超时
可以通过很多种方法创建一个Future来描述任务。ExecutorService中的所有submit方法都将返回一个Future,从而将一个Runnable 或 Callable 提交给Executor ,并得到一个Future 用来获得任务的执行结果或者取消任务。 还可以显示地为某个任务指定的Runnable或Callable 实例化一个FutureTask。
说明:
(01) RunnableFuture是一个接口,它继承了Runnable和Future这两个接口。RunnableFuture的源码如下:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
(02) FutureTask实现了RunnableFuture接口。所以,也说它实现了Future接口。
第3部分 示例和源码分析
先通过一个示例看看Callable和Future的基本用法,然后再分析示例的实现原理。
package com.concurrency.TaskExecution_6; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; /**
* Callable 和 Future实现线程等待
* @ClassName: CallableFutureTest
* @author Xingle
* @date 2014-9-15 下午3:23:30
*/
public class CallableFutureTest { public static void main(String[] args) throws InterruptedException, ExecutionException{
System.out.println("start main thread ");
ExecutorService exec = Executors.newFixedThreadPool(5); //新建一个Callable 任务,并将其提交到一个ExecutorService. 将返回一个描述任务情况的Future.
Callable<String> call = new Callable<String>() { @Override
public String call() throws Exception {
System.out.println("start new thread ");
Thread.sleep(5000);
System.out.println("end new thread ");
return "返回内容";
}
}; Future<String> task = exec.submit(call);
Thread.sleep(1000);
String retn = task.get();
//关闭线程池
exec.shutdown();
System.out.println(retn+"--end main thread");
} }
执行结果:
3.1 submit()
submit()在java/util/concurrent/AbstractExecutorService.java中实现,它的源码如下:
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// 创建一个RunnableFuture对象
RunnableFuture<T> ftask = newTaskFor(task);
// 执行“任务ftask”
execute(ftask);
// 返回“ftask”
return ftask;
}
说明:submit()通过newTaskFor(task)创建了RunnableFuture对象ftask。它的源码如下:
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
3.2 FutureTask的构造函数
FutureTask的构造函数如下:
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
// callable是一个Callable对象
this.callable = callable;
// state记录FutureTask的状态
this.state = NEW; // ensure visibility of callable
}
3.3 FutureTask的run()方法
继续回到submit()的源码中。
在newTaskFor()新建一个ftask对象之后,会通过execute(ftask)执行该任务。此时ftask被当作一个Runnable对象进行执行,最终会调用到它的run()方法;ftask的run()方法在java/util/concurrent/FutureTask.java中实现,源码如下:
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
// 将callable对象赋值给c。
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 执行Callable的call()方法,并保存结果到result中。
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
// 如果运行成功,则将result保存
if (ran)
set(result);
}
} finally {
runner = null;
// 设置“state状态标记”
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
说明:run()中会执行Callable对象的call()方法,并且最终将结果保存到result中,并通过set(result)将result保存。
之后调用FutureTask的get()方法,返回的就是通过set(result)保存的值。
第4部分 实例——JAVA并行异步编程线程池+FutureTask
通过上面的介绍,Callable 和 Future组合使用,可以获取线程的执行结果,实际项目中遇到一个问题,查询数据库的时候,有个执行任务返回的数据非常大,几乎是全部用户群的数据,所以非常耗时,后续处理逻辑要获取到所有返回的数据。这里考虑在查询数据的时候,加上行号,利用线程池多个任务同时查从起始行到结束行的数据,类似分页处理,最后将返回的数据合在一起。
@Override
public List<BuyerInfoVo> testQuery(final String eticketActId) { int count = grantEticketActDao.testQueryCount(eticketActId);
final int pageSize = 2000;
final int totalPage = count / pageSize+1; long start = System.currentTimeMillis();
// 进行异步任务列表
List<FutureTask<List<BuyerInfoVo>>> futureTasks = new ArrayList<FutureTask<List<BuyerInfoVo>>>();
// 线程池 初始化30个线程
ExecutorService executorService = Executors.newFixedThreadPool(30); Callable<List<BuyerInfoVo>> callable = new Callable<List<BuyerInfoVo>>() {
@Override
public List<BuyerInfoVo> call() throws Exception {
// 执行任务
List<BuyerInfoVo> ls = grantEticketActDao.testQueryBySize(eticketActId,pageSize,totalPage);
return ls;
}
}; List<BuyerInfoVo> reLs = new ArrayList<BuyerInfoVo>();
for (int i = 0; i < totalPage; i++) {
// 创建一个异步任务
FutureTask<List<BuyerInfoVo>> futureTask = new FutureTask<List<BuyerInfoVo>>(
callable);
futureTasks.add(futureTask);
executorService.submit(futureTask);
} for (FutureTask<List<BuyerInfoVo>> futureTask : futureTasks) {
try {
List<BuyerInfoVo> ls = futureTask.get();
if(null!=ls)
reLs.addAll(ls);
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
} // 清理线程池
executorService.shutdown(); long end = System.currentTimeMillis(); System.out.println("数据库查询记录总数:"+count);
System.out.println("实际返回数据条数: " + reLs.size());
System.out.println("一共使用时间:"+(end-start)/1000+"s");
if(reLs.size()!=count){
throw new RuntimeException();
}
GrantEticketActServiceImpl.queryId = 0;
return reLs;
}
其中,执行任务查询的代码:
public List<BuyerInfoVo> testQueryBySize(String eticketActId, int pageSize,
int totalPage) {
int start ;
int end;
synchronized (eticketActId) {
start = (GrantEticketActServiceImpl.queryId)*pageSize+1;
end = (GrantEticketActServiceImpl.queryId+1)*pageSize;
GrantEticketActServiceImpl.queryId++;
}
StringBuffer sb = new StringBuffer();
//查询语句省略
String querysql=" ";
sb.append(" SELECT * FROM (SELECT ROWNUM row_, t.* FROM (");
sb.append(querysql);
sb.append(") t ) WHERE row_ <=");
sb.append(end);
sb.append(" AND row_ >=");
sb.append(start);
String sql = sb.toString(); Object[] args = new Object[]{eticketActId };
List<BuyerInfoVo> list = jdbcTemplate.query(sql, args, new RowMapper<BuyerInfoVo>(){
@Override
public BuyerInfoVo mapRow(ResultSet rs, int i)
throws SQLException {
BuyerInfoVo vo = new BuyerInfoVo();
AutoInjection.Rs2Vo(rs, vo, null);
return vo;
}
}); return list;
}
执行结果说明,原先优化之前,单次执行返回时间需要4min,优化后返回只需15s,效果非常明显,涉及的参数,单次查询条数,以及线程池的配置大小,还有待继续深入。
第5部分 Callable和Future 区别
Callable定义的方法是call,而Runnable定义的方法是run。
Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。
Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。
参考:
1.《java 并发编程实战》 第六章 任务执行
5.Callable vs Runnable - Runners间的“争论”
项目中代码片段:
/**
* @Description:获取所有有效产品列表
* @return
* @Return:List<SupplyAreaProVo>
* @Author:
* @Date:2016年5月9日 下午12:33:49
*/
private List<SupplyAreaProVo> getTMallProductIndexAll(){
Util.i = 0;
final int total = 10;
List<FutureTask<List<SupplyAreaProVo>>> futureTasks = new ArrayList<FutureTask<List<SupplyAreaProVo>>>();
// 线程池 初始化20个线程
ExecutorService executorService = Executors.newFixedThreadPool(20);
List<SupplyAreaProVo> list=new ArrayList<SupplyAreaProVo>();
for (int index = 0; index < total; index++) {
final int cnt = index;
Callable<List<SupplyAreaProVo>> callable = new Callable<List<SupplyAreaProVo>>() {
@Override
public List<SupplyAreaProVo> call() throws Exception {
// 执行任务
List<SupplyAreaProVo> list= searchTLMallDao.getTMallProductList(total,cnt);
return list;
}
};
// 创建一个异步任务
FutureTask<List<SupplyAreaProVo>> futureTask = new FutureTask<List<SupplyAreaProVo>>(callable);
futureTasks.add(futureTask);
executorService.submit(futureTask);
} for (FutureTask<List<SupplyAreaProVo>> futureTask : futureTasks) {
try { List<SupplyAreaProVo> ls = futureTask.get();
if(null!=ls){
list.addAll(ls);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} }
executorService.shutdown();
return list;
}
并发编程 05—— Callable和Future的更多相关文章
- Java并发编程:Callable、Future和FutureTask
作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...
- (转)Java并发编程:Callable、Future和FutureTask
Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...
- Java并发编程:Callable、Future和FutureTask(转)
Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...
- 15、Java并发编程:Callable、Future和FutureTask
Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...
- 007 Java并发编程:Callable、Future和FutureTask
原文https://www.cnblogs.com/dolphin0520/p/3949310.html Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述 ...
- java并发编程--Runnable Callable及Future
1.Runnable Runnable是个接口,使用很简单: 1. 实现该接口并重写run方法 2. 利用该类的对象创建线程 3. 线程启动时就会自动调用该对象的run方法 通常在开发中结合Execu ...
- Java 并发编程:Callable和Future
项目中经常有些任务需要异步(提交到线程池中)去执行,而主线程往往需要知道异步执行产生的结果,这时我们要怎么做呢?用runnable是无法实现的,我们需要用callable实现. import java ...
- Java并发编程:Callable、Future和FutureTask的实现
启动线程执行任务,如果需要在任务执行完毕之后得到任务执行结果,可以使用从Java 1.5开始提供的Callable和Future 下面就分析一下Callable.Future以及FutureTask的 ...
- [转载] Java并发编程:Callable、Future和FutureTask
转载自http://www.cnblogs.com/dolphin0520/p/3949310.html 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Run ...
随机推荐
- html+css+javascript实现列表循环滚动示例代码
使用html+css+javascript实现列表循环滚动,设置时间定时,在规定的时间内替换前一个节点的内容,具体示例如下,感兴趣的朋友可以参考下 说明:设置时间定时,在规定的时间内替换前一个节点的内 ...
- lua OOP实现对象的链式调用
数学中的链式法则 http://sx.zxxk.com/ArticleInfo.aspx?InfoID=164649 链式微分法则:实数运算的链式法则:对数运算的链式法则:平行公理的链式法则:向量运算 ...
- [Android Tips] 9. framework notification layout font size
android 4.4 framework notification layout 相关字体大小 * title: notification_title_text_size: 18dp * conte ...
- bootstrap入门-2.固定的内置样式
HTML5文档类型(Doctype) Bootstrap使用了一些HTML5元素和CSS属性,所以需要使用HTML5文档类型. <!DOCTYPE html> <html> . ...
- linux挂载移动硬盘
1. 安装ntfs-3g2. mkdir /mnt/disk3. mount -t ntfs-3g /dev/sdb /mnt/disk4.卸载 umount /dev/sdb
- 改int非空自增列为int可为空列
) --声明读取数据库所有数据表名称游标mycursor1 open mycursor1 --从游标里取出数据赋值到我们刚才声明的数据表名变量中 fetch next from mycursor1 i ...
- uber 真是垃圾
uber司机好几次都不认识路,态度也不好,最开始使用是因为它价格最便宜,随着滴滴/快的价格下调,已经没有再使用uber的必要,果断卸载.
- 手机APP测试体系
- 互联网中级Javascript面试题
互联网中级Javascript面试题 1.实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number.String.Object.Array.Boolean)进行值复制 ...
- Centos7下安装配置Redsocks
Redsocks是一个开源的网络程序,代码依赖开源的libevent网络库.Redsocks允许你将所有TCP连接重定向到SOCKS或HTTPS代理,比如Shadowsocks(Centos7下安装配 ...