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. 应该具备的调试技能(java)

    ------Java部分---------- 1. tomcat在eclispe中怎样启动调试模式2. 带有main方法的Java应用程序怎样启动调试模式3. 调试在eclispe中的快捷键 F5 F ...

  2. Oracle EBS R12 电子技术参考手册 - eTRM (电子文档)

    http://etrm.oracle.com/pls/etrm/etrm_search.search

  3. 20145320《Java程序设计》第4周学习总结

    20145320<Java程序设计>第4周学习总结 教材学习内容总结 第六章 继承与多态 继承 继承作为面向对象的第二大特征,避免多个类间重复定义共同行为.即当多个类中存在相同属性和行为时 ...

  4. winform 计算器

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  5. RobotFrameWork WebService Soap接口测试 (一)

    在做完基于http协议的接口测试之后,开始弄soap协议了,之前有过开发java webservice自动化框架的经验,所以我想着应该并不会很难.对于webservice的简介,服务器端和客户端的开发 ...

  6. Maps for Developers

    苹果官方文档: Give your apps a sense of place with maps and location information. Present maps with custom ...

  7. [转]ASP.NET MVC Json()处理大数据异常解决方法 json maxjsonlength

    本文转自:http://blog.csdn.net/blacksource/article/details/18797055 先对项目做个简单介绍: 整个项目采用微软的ASP.NET MVC3进行开发 ...

  8. webApp移动开发之REM

    最近发现一偏很好的文章,关于webAPP开发REM 一个css单位: 来自腾讯ISUX; web app变革之rem

  9. C#写WPF程序,SQLSever2008 提示插入数据成功,却查询不到数据

    1.提示存储成功,但是数据库中没有数据.今天在用C#写一个wpf程序时,要向数据库Sql server2008 中插入数据,程序提示成功,但打开数据库却没有值; 经过查询发现,数据存储到默认路径下的数 ...

  10. js基础的知识整理

    一.操作样式: .style   操作行间样式 .className 修改class 二.操作属性 1. .  更简单,操作已有的属性 2. [] 更灵活,点能做的,方括号都能做.方括号中放的是字符串 ...