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. html+css+javascript实现列表循环滚动示例代码

    使用html+css+javascript实现列表循环滚动,设置时间定时,在规定的时间内替换前一个节点的内容,具体示例如下,感兴趣的朋友可以参考下 说明:设置时间定时,在规定的时间内替换前一个节点的内 ...

  2. lua OOP实现对象的链式调用

    数学中的链式法则 http://sx.zxxk.com/ArticleInfo.aspx?InfoID=164649 链式微分法则:实数运算的链式法则:对数运算的链式法则:平行公理的链式法则:向量运算 ...

  3. [Android Tips] 9. framework notification layout font size

    android 4.4 framework notification layout 相关字体大小 * title: notification_title_text_size: 18dp * conte ...

  4. bootstrap入门-2.固定的内置样式

    HTML5文档类型(Doctype) Bootstrap使用了一些HTML5元素和CSS属性,所以需要使用HTML5文档类型. <!DOCTYPE html> <html> . ...

  5. linux挂载移动硬盘

    1. 安装ntfs-3g2. mkdir /mnt/disk3. mount -t ntfs-3g /dev/sdb /mnt/disk4.卸载 umount /dev/sdb

  6. 改int非空自增列为int可为空列

    ) --声明读取数据库所有数据表名称游标mycursor1 open mycursor1 --从游标里取出数据赋值到我们刚才声明的数据表名变量中 fetch next from mycursor1 i ...

  7. uber 真是垃圾

    uber司机好几次都不认识路,态度也不好,最开始使用是因为它价格最便宜,随着滴滴/快的价格下调,已经没有再使用uber的必要,果断卸载.

  8. 手机APP测试体系

  9. 互联网中级Javascript面试题

    互联网中级Javascript面试题 1.实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number.String.Object.Array.Boolean)进行值复制 ...

  10. Centos7下安装配置Redsocks

    Redsocks是一个开源的网络程序,代码依赖开源的libevent网络库.Redsocks允许你将所有TCP连接重定向到SOCKS或HTTPS代理,比如Shadowsocks(Centos7下安装配 ...