利用直接在一个线程中求和是效率非常低的,我们通过栅栏,先将这个问题拆分成一系列相互独立的子问题,通过栅栏后,将子问题的问题汇集起来,进行全部的求解。
CyclicBarrier是一个很好的实现,它的构造方法中有一个Runnable参数,这个是最后进行汇总的方法,比如我们最后将计算结果求和,这里就是求和的执行。
await()方法利用栅栏特性,等待所有线程求完元素之和再计算平均值。
书上具体的介绍:
CyclicBarrier可以使一定数量的参与方反复地在栅栏位置汇集,它在并行迭代算法中非常有用:这种算法通常将一个问题拆分成一系列相互独立的子问题。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达了栅栏 ,那么栅栏将打开,此时所有线程都被释放,而栅栏将被重置以便下次使用。如果对await的调用超时,后者await阻塞的线程被中断,那么栅栏就被认为是打破了,所有阻塞的await调用都将终止并抛出BrokenBarrirerException。如果成功地通过栅栏,那么await将为每个线程返回一个唯一的到达索引号,我们可以利用这些索引来“选举”产生一个领导线程,并在下一次迭代中由该领导线程执行一些特殊的工作。CyclicBarrier还可以使你将一个栅栏操作传递给构造函数,这是一个Runnable,当成功通过栅栏会(在一个子任务线程中)执行它,但在阻塞线程被释放之前是不能执行的。
以下是我写的一个代码小例子:
package com.cc.mutilineExample.callableAndFuture.test4_CyclicBarrier;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CyclicBarrier;
/**
* @ClassName: FutureResponseTest
* @Description: 根据交易请求的额度和方式,进行扣款汇总计算
* 计算交易一共扣款的数额:
* 利用直接在一个线程中求和是效率非常低的,我们通过栅栏,先将这个问题拆分成一系列相互独立的子问题,通过
* 栅栏后,将子问题的问题汇集起来,进行全部的求解。
*
* 差不多,但是将最后汇总也包含进去了框架
*
* 同样的,还是有些繁琐
* @author CC
* @date 2018年12月6日 上午10:44:20
* @version V1.0
*/
public class CyclicBarrierTest {
private final static int SIZE = 20;//交易请求数
private final Double[] doubleArr = new Double[SIZE];//结果存储集
private final CyclicBarrier barrier;//栅栏
private final Worker[] workers;//子任务线程
private final List requestList;//交易请求集
//此方式的消耗时间不能直接写在调用那里了,异步了,计算不准确,放在这里
private final long startTime = System.currentTimeMillis();//开始时间
private static long endTime ;//结束时间
//初始化
public CyclicBarrierTest(List requestList) {
barrier = new CyclicBarrier(SIZE, new Runnable() {
/*
* (非 Javadoc)
* Title: run
* Description: 求和
* 当成功通过栅栏会(在一个子任务线程中,即只会进行一次)执行它,但在阻塞线程被释放之前是不能执行的。
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
try {
BigDecimal sum = BigDecimal.ZERO;//同理 double计算结果不精确
//方法get具有“状态依赖”的内在特性,因而调用者不需要知道任务的状态,此外在任务提交和获得
//结果中包含的安全发布属性也确保了这个方法是线程安全的。
//获得结果
for (int i = 0; i < doubleArr.length; i++) {
//提交任务请求
double payMent = doubleArr[i];
sum = sum.add(new BigDecimal(String.valueOf(payMent)));
}
System.out.println("一共扣款了多少钱?" + sum.doubleValue());
endTime = System.currentTimeMillis();
System.out.println("消耗时间:" + (endTime - startTime) + "毫秒!");
} catch (Exception e) {
e.printStackTrace();
}
}
});
this.requestList = requestList;//初始化交易请求,方便子任务线程使用
this.workers = new Worker[SIZE];//初始化子任务线程,创建线程
for (int i = 0; i < SIZE; i++) {
workers[i] = new Worker(i);
}
}
//子任务线程--即调用第三方服务
private class Worker implements Runnable{
private int index;//每个线程带自己的请求
public Worker(int index) {
this.index = index;
}
/* (非 Javadoc)
* Title: run
* Description: 调用第三方服务
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
try {
//根据index获取request,调用第三方服务
double result = CyclicBarrierTest.requestForService(requestList.get(index));
//结果存储在数组中
doubleArr[index] = result;
} catch (Exception e) {
e.printStackTrace();
}
try {
barrier.await();//利用栅栏特性,等待所有线程求完元素之和再计算平均值
} catch (Exception e) {
return;
}
}
}
//开始执行--程序入口
public void start() {
for (int i = 0; i < workers.length; i++) {
new Thread(workers[i]).start();;
}
}
//模拟第三方服务
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;
}
//调度请求,获得返回结果,并进行汇总处理
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 requestList = new ArrayList();
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);
}
//累积计算所有请求的总扣款数--计算任务提前开始且每个都是分开的不相互影响
CyclicBarrierTest test = new CyclicBarrierTest(requestList);
test.start();
}
}
运行结果:
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
开始处理请求…
微信支付扣3%
开始处理请求…
微信支付扣3%
开始处理请求…
微信支付扣3%
开始处理请求…
其他支付直接扣5%
开始处理请求…
微信支付扣3%
开始处理请求…
微信支付扣3%
开始处理请求…
开始处理请求…
其他支付直接扣5%
微信支付扣3%
微信支付扣3%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
开始处理请求…
其他支付直接扣5%
Request [method=WangYin, servieName=TaoBao, param=43] 返回扣款结果:2.15
Request [method=ZhiFuBao, servieName=TaoBao, param=179] 返回扣款结果:8.95
Request [method=WeiXin, servieName=JingDong, param=90] 返回扣款结果:2.7
Request [method=WeiXin, servieName=TianMao, param=76] 返回扣款结果:2.28
Request [method=WeiXin, servieName=JingDong, param=212] 返回扣款结果:6.36
Request [method=ZhiFuBao, servieName=TianMao, param=11] 返回扣款结果:0.55
Request [method=ZhiFuBao, servieName=TianMao, param=2] 返回扣款结果:0.1
Request [method=WangYin, servieName=TaoBao, param=291] 返回扣款结果:14.55
Request [method=WeiXin, servieName=JingDong, param=149] 返回扣款结果:4.47
Request [method=ZhiFuBao, servieName=TaoBao, param=276] 返回扣款结果:13.8
Request [method=WangYin, servieName=JingDong, param=228] 返回扣款结果:11.4
Request [method=WangYin, servieName=JingDong, param=293] 返回扣款结果:14.65
Request [method=WangYin, servieName=JingDong, param=127] 返回扣款结果:6.35
Request [method=ZhiFuBao, servieName=TaoBao, param=2] 返回扣款结果:0.1
Request [method=WangYin, servieName=TaoBao, param=284] 返回扣款结果:14.2
Request [method=WeiXin, servieName=TianMao, param=58] 返回扣款结果:1.74
Request [method=WeiXin, servieName=TaoBao, param=194] 返回扣款结果:5.82
Request [method=WeiXin, servieName=TaoBao, param=180] 返回扣款结果:5.4
Request [method=ZhiFuBao, servieName=JingDong, param=183] 返回扣款结果:9.15
Request [method=ZhiFuBao, servieName=TianMao, param=223] 返回扣款结果:11.15
一共扣款了多少钱?135.87
消耗时间:3029毫秒!
可以发现,实现了效率的提升,并且任务的提交和任务的执行是分开来的。
但是也有缺点,就是程序太复杂,在多线程中还可以用Callable/Future的方式来实现这个例子,下一章介绍。