Java并发编程实践 目录

并发编程 01—— ThreadLocal

并发编程 02—— ConcurrentHashMap

并发编程 03—— 阻塞队列和生产者-消费者模式

并发编程 04—— 闭锁CountDownLatch 与 栅栏CyclicBarrier

并发编程 05—— Callable和Future

并发编程 06—— CompletionService : Executor 和 BlockingQueue

并发编程 07—— 任务取消

并发编程 08—— 任务取消 之 中断

并发编程 09—— 任务取消 之 停止基于线程的服务

并发编程 10—— 任务取消 之 关闭 ExecutorService

并发编程 11—— 任务取消 之 “毒丸”对象

并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性

并发编程 13—— 线程池的使用 之 配置ThreadPoolExecutor 和 饱和策略

并发编程 14—— 线程池 之 整体架构

并发编程 15—— 线程池 之 原理一

并发编程 16—— 线程池 之 原理二

并发编程 17—— Lock

并发编程 18—— 使用内置条件队列实现简单的有界缓存

并发编程 19—— 显式的Conditon 对象

并发编程 20—— AbstractQueuedSynchronizer 深入分析

并发编程 21—— 原子变量和非阻塞同步机制

概述

第1部分 Callable

第2部分 Future

第3部分 示例和源码分析

  3.1 submit()

  3.2 FutureTask的构造函数

  3.3 FutureTask的run()方法

第4部分 实例——JAVA并行异步编程线程池+FutureTask

第5部分 Callable和Future 区别

参考

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 并发编程实战》 第六章 任务执行

2.java并发编程-Executor框架

3.Java线程(七):Callable和Future

4.JAVA并行异步编程线程池+FutureTask

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的更多相关文章

  1. Java并发编程:Callable、Future和FutureTask

    作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...

  2. (转)Java并发编程:Callable、Future和FutureTask

    Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...

  3. Java并发编程:Callable、Future和FutureTask(转)

    Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...

  4. 15、Java并发编程:Callable、Future和FutureTask

    Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...

  5. 007 Java并发编程:Callable、Future和FutureTask

    原文https://www.cnblogs.com/dolphin0520/p/3949310.html Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述 ...

  6. java并发编程--Runnable Callable及Future

    1.Runnable Runnable是个接口,使用很简单: 1. 实现该接口并重写run方法 2. 利用该类的对象创建线程 3. 线程启动时就会自动调用该对象的run方法 通常在开发中结合Execu ...

  7. Java 并发编程:Callable和Future

    项目中经常有些任务需要异步(提交到线程池中)去执行,而主线程往往需要知道异步执行产生的结果,这时我们要怎么做呢?用runnable是无法实现的,我们需要用callable实现. import java ...

  8. Java并发编程:Callable、Future和FutureTask的实现

    启动线程执行任务,如果需要在任务执行完毕之后得到任务执行结果,可以使用从Java 1.5开始提供的Callable和Future 下面就分析一下Callable.Future以及FutureTask的 ...

  9. [转载] Java并发编程:Callable、Future和FutureTask

    转载自http://www.cnblogs.com/dolphin0520/p/3949310.html 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Run ...

随机推荐

  1. SVD奇异值分解

    奇异值分解 备忘:Eigen类库可能会和其他库产生冲突,将Eigen类库的头文件引用放到前面解决了.

  2. svn更新报错:svn unable to connect to a repository at url

    出现错误:unable to connect to a repository at url 解决办法1. 右键点击本地副本,TortoiseSVN -> Settings -> Saved ...

  3. mongodb的一些基本操作

    1.列出所有数据库 >show dbs   2.使用数据库 >use memo   3.列出当前数据库的collections >show collections   4.显示当前正 ...

  4. mysqladmin note

    hr,fresh meat!! --------------------------------------------------- 15 Practical Usages of Mysqladmi ...

  5. EXCEL导入导出自己整理的一些方法

    //导入Excel代码 protected DataTable ExcelHelper(string filePaht) { string sFilePath2003 = Server.MapPath ...

  6. 关于为busybox设置setuid

    安卓root了,重启之后就没root权限了,于是想到了为 busybox 设置 setuid 来实现使用root的身份执行命令. 可是,不管用啊,还是提示没有权限.搜了一下 busybox setui ...

  7. 使用AppCan实现分享网站功能

    使用AppCan实现分享网站功能 昨天我们实现了最基本的文字分享功能,今天呢,我们来实现基本的分享网站功能: 微信指引部分这里不再复述,具体请参见:http://newdocx.appcan.cn/i ...

  8. [转]SOCKET通信中TCP、UDP数据包大小的确定

    TCP.UDP数据包大小的确定 UDP和TCP协议利用端口号实现多项应用同时发送和接收数据.数据通过源端口发送出去,通过目标端口接收.有的网络应用只能使用预留或注册的静态端口:而另外一些网络应用则可以 ...

  9. ALLOCATE语句分配FORTRAN动态数组方法(转自http://blog.csdn.net/zhuxianjianqi/article/details/8067174)

    数组的动态分配 a)    可分配数组 数组可以是静态的也可以是动态的.如果数组是静态的,则在编译时就被分配了固定的储存空间,并且直到程序退出时才被释放.程序运行时静态数组的大小不能改变.静态数组的缺 ...

  10. Unity中通过类名字符串取组件类型的方法(Types.GetType用法)

    正常调用Type.GetType取不到组件,因为会先创建实例在获取,而Unity组件无法通过new来创建. 第二种创建方式是通过程序集,具体如下 Assembly.GetExecutingAssemb ...