有的时候我们希望在一定时间内获取这个任务的结果,如果没有获取到就结束这个任务。这个时候需要对任务的时间做一个限制。
- Future.get(long timeout, TimeUnit unit)
future.get方法提供了这样的参数,当结果可用时,它将立即返回,如果在指定时限内没有计算出结果,那么将抛出TimeoutException。当任务超时后,future也提供了一定的方法来取消任务,如果任务是可取消的,那么可以提前中止它,避免资源的浪费。
之前的例子稍微改一下,即可:
package com.cc.test.timelimit;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
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;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: TimeLimitTaskTest
* @Description: 在指定时间内获取信息
* @author CC
* @date 2018年12月12日 上午10:00:05
* @version V1.0
*/
public class TimeLimitTaskTest {
private final static long TIME_BUDGET = 2;//超时时间:5 将此设置为2,可以出现超时异常,程序还在运行,只是这次的统计结果并没有出现了,bingo
private final static TimeUnit Time_Unit = TimeUnit.SECONDS;//超时单位 秒
//线程池
private final static ExecutorService executor = Executors.newCachedThreadPool();
//调度请求,获得返回结果,并进行汇总处理
public static void main(String[] args) throws Exception {
final String[] methodStr = new String[] {"WeiXin","ZhiFuBao","WangYin"};
final String[] serviceStr = new String[] {"TaoBao","JingDong","TianMao"};
//为了方便,我们将请求先初始化完毕
final List<Request> requestList = new ArrayList<Request>();
for (int i = 0; i < 20; i++) {
Request request = new Request();
request.setMethod(methodStr[(int) (Math.random() * 3)]);
request.setParam((int) (Math.random() * 300));
request.setServieName(serviceStr[(int) (Math.random() * 3)]);
requestList.add(request);
}
long startTime = System.currentTimeMillis();//开始时间
//累积计算所有请求的总扣款数--计算任务提前开始且每个都是分开的不相互影响
List<Future<Double>> futureList = new ArrayList<Future<Double>>();
for (int i = 0; i < requestList.size(); i++) {
Request request = requestList.get(i);
Callable<Double> task = new Callable<Double>() {
@Override
public Double call() throws Exception {
return requestForService(request);
}
};
Future<Double> future = executor.submit(task);
futureList.add(future);
}
try {
BigDecimal sum = BigDecimal.ZERO;//同理 double计算结果不精确
//方法get具有“状态依赖”的内在特性,因而调用者不需要知道任务的状态,此外在任务提交和获得
//结果中包含的安全发布属性也确保了这个方法是线程安全的。
//获得结果
for (int i = 0; i < futureList.size(); i++) {
//提交任务请求
/*
* 传递给get的timeout参数的计算方法是,将制定时限减去当前时间。这可能会得到负数,但java.util.concurrent中所有与
* 时限相关的方法都将负数视为零,因此不需要额外的代码来处理这种情况。
*/
double payMent = 0.0;
try {
payMent = futureList.get(i).get(TIME_BUDGET, Time_Unit);
} catch (TimeoutException e) {
futureList.get(i).cancel(true);//Future.cancel的参数为true表示任务线程可以在运行过程中中断,参见第七章
throw launderThrowable("任务超时,超时时间:" + TIME_BUDGET + " " + Time_Unit);
}
sum = sum.add(new BigDecimal(String.valueOf(payMent)));
}
System.out.println("一共扣款了多少钱?" + sum.doubleValue());
} catch (InterruptedException e) {
// TODO: 任务调用get的线程在获得结果之前被中断
//重新设置线程的中断状态
Thread.currentThread().interrupt();
System.out.println("任务调用get的线程在获得结果之前被中断!" + e);
} catch (ExecutionException e) {
//所有线程结束
executor.shutdown();
System.err.println("有任务发生异常,所有任务线程结束!");
throw launderThrowable(e.getMessage());
}
long endTime = System.currentTimeMillis();//结束时间
System.out.println("消耗时间:" + (endTime - startTime) + "毫秒!");
}
//模拟第三方服务
public static double requestForService(Request request) throws InterruptedException, Exception{
if(null == request) {
throw new Exception("请求为空!");
}
if(request.getParam() <= 0) {
throw new Exception("参数小于0,无法进行扣款!" + request);
}
System.out.println("开始处理请求...");
//为了简便直接返回一个结果即可
double result = 0.0;
if("WeiXin".equals(request.getMethod())) {
System.out.println("微信支付扣3%");
// result = request.getParam() * 0.03;//double类型计算结果不准确 例如17 * 0.05 返回 扣款数 0.8500000000000001
result = new BigDecimal(String.valueOf(request.getParam())).multiply(new BigDecimal("0.03")).doubleValue();
}else {
System.out.println("其他支付直接扣5%");
result = new BigDecimal(String.valueOf(request.getParam())).multiply(new BigDecimal("0.05")).doubleValue();
}
//模拟-使消耗时间长一些
Thread.sleep(3000);
System.out.println(request + " 返回扣款结果:" + result);
return result;
}
/**
* @Title: launderThrowable
* @Description: 任务执行过程中遇到异常,根据包装的ExecutionException重新抛出异常,并打印异常信息
* @param @param cause
* @param @return
* @return Exception
* @throws
* @author CC
* @date 2018年12月7日 上午9:36:27
* @version V1.0
*/
private static Exception launderThrowable(String cause) {
//抛出
Exception exception = null;
if(null == cause) {
exception = new Exception("任务执行过程中遇到异常!");
}else {
exception = new Exception("任务执行过程中遇到异常!" + cause);
}
return exception;
}
}
运行结果:
开始处理请求…
微信支付扣3%
开始处理请求…
微信支付扣3%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
微信支付扣3%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
微信支付扣3%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
微信支付扣3%
开始处理请求…
微信支付扣3%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
Exception in thread “main” java.lang.Exception: 任务执行过程中遇到异常!任务超时,超时时间:2 SECONDS
at com.cc.test.timelimit.TimeLimitTaskTest.launderThrowable(TimeLimitTaskTest.java:141)
at com.cc.test.timelimit.TimeLimitTaskTest.main(TimeLimitTaskTest.java:75)
Request [method=ZhiFuBao, servieName=JingDong, param=87] 返回扣款结果:4.35
Request [method=WangYin, servieName=JingDong, param=272] 返回扣款结果:13.6
Request [method=WangYin, servieName=TianMao, param=45] 返回扣款结果:2.25
Request [method=WeiXin, servieName=TianMao, param=239] 返回扣款结果:7.17
Request [method=WangYin, servieName=TianMao, param=29] 返回扣款结果:1.45
Request [method=WangYin, servieName=TianMao, param=171] 返回扣款结果:8.55
Request [method=WeiXin, servieName=TianMao, param=252] 返回扣款结果:7.56
Request [method=WeiXin, servieName=TaoBao, param=127] 返回扣款结果:3.81
Request [method=ZhiFuBao, servieName=TianMao, param=97] 返回扣款结果:4.85
Request [method=WeiXin, servieName=TianMao, param=86] 返回扣款结果:2.58
Request [method=WangYin, servieName=TaoBao, param=4] 返回扣款结果:0.2
Request [method=WangYin, servieName=JingDong, param=52] 返回扣款结果:2.6
Request [method=WeiXin, servieName=JingDong, param=18] 返回扣款结果:0.54
Request [method=WangYin, servieName=JingDong, param=244] 返回扣款结果:12.2
Request [method=ZhiFuBao, servieName=JingDong, param=216] 返回扣款结果:10.8
Request [method=ZhiFuBao, servieName=TianMao, param=279] 返回扣款结果:13.95
Request [method=ZhiFuBao, servieName=TaoBao, param=89] 返回扣款结果:4.45
Request [method=WangYin, servieName=TaoBao, param=297] 返回扣款结果:14.85
Request [method=WangYin, servieName=JingDong, param=236] 返回扣款结果:11.8
- executor.invokeAll
executor中的invokeAll方法提供了一个更好的实现。
书中描述:
InvokeAll方法的参数为一组任务,并返回一组Future。这两个集合有着相同的结构。InvokeAll按照任务集合中迭代器的顺序将所有的Future添加到返回的集合中,从而使调用者能将各个Future与其表示的Callable关联起来。当所有任务都执行完毕时,或者调用线程被中断时,又或者超过指定时限时,invokeAll将返回。当超过指定时限时,任何还未完成的任务都会被取消。当invokeAll返回后,每个任务要么正常地完成,要么被取消,而客户端代码可以调用get或isCancelled来判断究竟是何种情况。
之前例子改下,如下:
package com.cc.test.timelimit2;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: TimeLimitTaskTest
* @Description: 在指定时间内获取信息
* @author CC
* @date 2018年12月12日 上午10:00:05
* @version V1.0
*/
public class TimeLimitTaskTest {
private final static long TIME_BUDGET = 2;//超时时间:5 将此设置为2,可以出现超时异常,程序还在运行,只是这次的统计结果并没有出现了,bingo
private final static TimeUnit Time_Unit = TimeUnit.SECONDS;//超时单位 秒
//线程池
private final static ExecutorService executor = Executors.newCachedThreadPool();
//调度请求,获得返回结果,并进行汇总处理
public static void main(String[] args) throws Exception {
final String[] methodStr = new String[] {"WeiXin","ZhiFuBao","WangYin"};
final String[] serviceStr = new String[] {"TaoBao","JingDong","TianMao"};
//为了方便,我们将请求先初始化完毕
final List<Request> requestList = new ArrayList<Request>();
for (int i = 0; i < 20; i++) {
Request request = new Request();
request.setMethod(methodStr[(int) (Math.random() * 3)]);
request.setParam((int) (Math.random() * 300));
request.setServieName(serviceStr[(int) (Math.random() * 3)]);
requestList.add(request);
}
long startTime = System.currentTimeMillis();//开始时间
//累积计算所有请求的总扣款数--计算任务提前开始且每个都是分开的不相互影响
List<Callable<Double>> tasks = new ArrayList<Callable<Double>>();//任务集参数
for (int i = 0; i < requestList.size(); i++) {
Request request = requestList.get(i);
Callable<Double> task = new Callable<Double>() {
@Override
public Double call() throws Exception {
return requestForService(request);
}
};
tasks.add(task);
}
List<Future<Double>> futures = executor.invokeAll(tasks, TIME_BUDGET, Time_Unit);//执行任务
BigDecimal sum = BigDecimal.ZERO;
for (int i = 0; i < futures.size(); i++) {
try {
//获得结果
double payMent = futures.get(i).get();
sum = sum.add(new BigDecimal(String.valueOf(payMent)));
} catch (InterruptedException e) {
// TODO: 任务调用get的线程在获得结果之前被中断
//重新设置线程的中断状态
Thread.currentThread().interrupt();
System.out.println("任务调用get的线程在获得结果之前被中断!" + e);
} catch(CancellationException e) {
//任务取消发生异常 - 任务超时也进入了这里
throw launderThrowable("任务超时!" + e.getMessage());
} catch(Exception e) {
//其他不可预料异常
throw launderThrowable("任务发生异常!" + e.getMessage());
}
}
System.out.println("一共扣款了多少钱?" + sum.doubleValue());
long endTime = System.currentTimeMillis();//结束时间
System.out.println("消耗时间:" + (endTime - startTime) + "毫秒!");
}
//模拟第三方服务
public static double requestForService(Request request) throws InterruptedException, Exception{
if(null == request) {
throw new Exception("请求为空!");
}
if(request.getParam() <= 0) {
throw new Exception("参数小于0,无法进行扣款!" + request);
}
System.out.println("开始处理请求...");
//为了简便直接返回一个结果即可
double result = 0.0;
if("WeiXin".equals(request.getMethod())) {
System.out.println("微信支付扣3%");
// result = request.getParam() * 0.03;//double类型计算结果不准确 例如17 * 0.05 返回 扣款数 0.8500000000000001
result = new BigDecimal(String.valueOf(request.getParam())).multiply(new BigDecimal("0.03")).doubleValue();
}else {
System.out.println("其他支付直接扣5%");
result = new BigDecimal(String.valueOf(request.getParam())).multiply(new BigDecimal("0.05")).doubleValue();
}
//模拟-使消耗时间长一些
Thread.sleep(3000);
System.out.println(request + " 返回扣款结果:" + result);
return result;
}
/**
* @Title: launderThrowable
* @Description: 任务执行过程中遇到异常,根据包装的ExecutionException重新抛出异常,并打印异常信息
* @param @param cause
* @param @return
* @return Exception
* @throws
* @author CC
* @date 2018年12月7日 上午9:36:27
* @version V1.0
*/
private static Exception launderThrowable(String cause) {
//抛出
Exception exception = null;
if(null == cause) {
exception = new Exception("任务执行过程中遇到异常!");
}else {
exception = new Exception("任务执行过程中遇到异常!" + cause);
}
return exception;
}
}
运行结果:
运行结果:
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
微信支付扣3%
开始处理请求…
其他支付直接扣5%
开始处理请求…
微信支付扣3%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
微信支付扣3%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
微信支付扣3%
开始处理请求…
微信支付扣3%
开始处理请求…
其他支付直接扣5%
Exception in thread “main” java.lang.Exception: 任务执行过程中遇到异常!任务超时!null
at com.cc.test.timelimit2.TimeLimitTaskTest.launderThrowable(TimeLimitTaskTest.java:130)
at com.cc.test.timelimit2.TimeLimitTaskTest.main(TimeLimitTaskTest.java:74)
(程序还在运行中)