ForkJoin
ForkJoin是由JDK1.7后提供多线并发处理框架,ForkJoin的框架的基本思想是分而治之。使用ForkJoin将相同的计算任务通过多线程的进行执行。从而能提高数据的计算速度。
分而治之
分而治之就是将一个复杂的计算,按照设定的阈值进行分解成多个计算,然后将各个计算结果进行汇总。相应的ForkJoin将复杂的计算当做一个任务。而分解的多个计算则是当做一个子任务。
使用
使用ForkJoin框架,需要创建一个ForkJoin的任务。因为ForkJoin框架为我们提供了RecursiveAction和RecursiveTask。
我们只需要继承ForkJoin为我们提供的抽象类的其中一个并且实现compute方法。
RecursiveTask在进行exec之后会使用一个result的变量进行接受返回的结果。而RecursiveAction在exec后是不会保存返回结果。
任务分割
ForkJoinTask
: 基本任务,使用forkjoin框架必须创建的对象,提供fork,join操作,常用的两个子类
RecursiveAction
: 无结果返回的任务RecursiveTask
: 有返回结果的任务
说明:
fork
: 让task异步执行join
: 让task同步执行,可以获取返回值- ForkJoinTask 在不显示使用ForkJoinPool.execute/invoke/submit()方法进行执行的情况下,也可以使用自己的fork/invoke方法进行执行
结果合并
ForkJoinPool
执行 ForkJoinTask
,
- 任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。
- 当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务
三中提交方式:
execute
异步,无返回结果submit
异步,有返回结果 (返回Future<T>
)invoke
同步,有返回结果 (会阻塞)
package com.bxwell.hj360.device.module.devicedatamanagement.service.impl;
import com.bxwell.hj360.common.base.common.until.StringUtil;
import com.bxwell.hj360.common.model.device.BxWellDeviceFactor;
import com.bxwell.hj360.module.devicedatamanagement.vo.DayDataShowStrVO;
import com.bxwell.hj360.module.devicedatamanagement.vo.HourDataShowStrVO;
import com.bxwell.hj360.module.devicedatamanagement.vo.MinDataShowStrVO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ObjectUtils;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class TimeForkJoinPoolCalcRate {
class CalcTask extends RecursiveAction{
private final Integer MAX = 100;
private String deviceId;
String factorId;
private Map<String, BigDecimal> unitRateMap;
private Map<String, BxWellDeviceFactor> cunit;
private List<MinDataShowStrVO> listMinDataShowVO;
private List<HourDataShowStrVO> listHourDataShowVO;
private List<DayDataShowStrVO> listDayDataShowVO;
private BigDecimal rateBig;
private BigDecimal totalUnitIdRateBig;
public CalcTask(String deviceId,String factorId ,Map<String, BigDecimal> unitRateMap,Map<String, BxWellDeviceFactor> cunit,
List<MinDataShowStrVO> listMinDataShowVO, List<HourDataShowStrVO> listHourDataShowVO, List<DayDataShowStrVO> listDayDataShowVO,BigDecimal rateBig,BigDecimal totalUnitIdRateBig){
this.deviceId = deviceId;
this.factorId = factorId;
this.unitRateMap = unitRateMap;
this.cunit = cunit;
this.listMinDataShowVO = listMinDataShowVO;
this.listHourDataShowVO = listHourDataShowVO;
this.listDayDataShowVO = listDayDataShowVO;
this.rateBig = rateBig;
this.totalUnitIdRateBig = totalUnitIdRateBig;
}
@Override
protected void compute() {
if(listMinDataShowVO != null){
if(listMinDataShowVO.size() <= MAX){
handleMinData(listMinDataShowVO);
}else {
handleThreadNum(listMinDataShowVO);
}
}else if(listHourDataShowVO != null){
if(listHourDataShowVO.size() <= MAX){
handleHourData(listHourDataShowVO);
}else {
handleThreadNum(listHourDataShowVO);
}
}else if(listDayDataShowVO != null){
if(listDayDataShowVO.size() <= MAX){
handleDayData(listDayDataShowVO);
}else {
handleThreadNum(listDayDataShowVO);
}
}
}
private void handleThreadNum(List<?> list){
createCalcTask(list,list.size() <= 1000 ? 5 : list.size() <= 10000 ? 10 : list.size() <= 50000 ? 25 : list.size() <= 100000 ? 50 : 100);
}
private void handleMinData(List<MinDataShowStrVO> list){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for(MinDataShowStrVO vo : list){
if(!StringUtil.isEmpty(vo.getEndTime())){
vo.setTimeStr(format.format(vo.getTime())+"-"+vo.getEndTime());
}
if(null != rateBig){
if(null != vo.getMin()){
vo.setMin(handleRateNum(vo.getMin(),rateBig,true));
}
if(null != vo.getMax()){
vo.setMax(handleRateNum(vo.getMax(),rateBig,true));
}
if(null != vo.getAvg()){
vo.setAvg(handleRateNum(vo.getAvg(),rateBig,true));
}
}else{//如果转换率的两个单位一致 导致取不到转换率 也需要保留有效小数点
if(null != vo.getMin()){
vo.setMin(handleRateNum(vo.getMin(),null,false));
}
if(null != vo.getMax()){
vo.setMax(handleRateNum(vo.getMax(),null,false));
}
if(null != vo.getAvg()){
vo.setAvg(handleRateNum(vo.getAvg(),null,false));
}
}
if(null != totalUnitIdRateBig && null != vo.getCou()){
vo.setCou(handleRateNum(vo.getCou(),totalUnitIdRateBig,true));
}
if(null == totalUnitIdRateBig && null != vo.getCou()){
//前端需处理成斜杠
vo.setCou(handleRateNum(vo.getCou(),null,false));
}
}
}
private void handleHourData(List<HourDataShowStrVO> list){
for(HourDataShowStrVO vo : list){
if(null != rateBig){
if(null != vo.getMin()){
vo.setMin(handleRateNum(vo.getMin(),rateBig,true));
}
if(null != vo.getMax()){
vo.setMax(handleRateNum(vo.getMax(),rateBig,true));
}
if(null != vo.getAvg()){
vo.setAvg(handleRateNum(vo.getAvg(),rateBig,true));
}
}else{//如果转换率的两个单位一致 导致取不到转换率 也需要保留有效小数点
if(null != vo.getMin()){
vo.setMin(handleRateNum(vo.getMin(),null,false));
}
if(null != vo.getMax()){
vo.setMax(handleRateNum(vo.getMax(),null,false));
}
if(null != vo.getAvg()){
vo.setAvg(handleRateNum(vo.getAvg(),null,false));
}
}
if(null != totalUnitIdRateBig && null != vo.getCou()){
vo.setCou(handleRateNum(vo.getCou(),totalUnitIdRateBig,true));
}
if(null == totalUnitIdRateBig && null != vo.getCou()){
//前端需处理成斜杠
vo.setCou(handleRateNum(vo.getCou(),null,false));
}
}
}
private void handleDayData(List<DayDataShowStrVO> list){
for(DayDataShowStrVO vo : list){
if(null != rateBig){
if(null != vo.getMin()){
vo.setMin(handleRateNum(vo.getMin(),rateBig,true));
}
if(null != vo.getMax()){
vo.setMax(handleRateNum(vo.getMax(),rateBig,true));
}
if(null != vo.getAvg()){
vo.setAvg(handleRateNum(vo.getAvg(),rateBig,true));
}
}else{//如果转换率的两个单位一致 导致取不到转换率 也需要保留有效小数点
if(null != vo.getMin()){
vo.setMin(handleRateNum(vo.getMin(),null,false));
}
if(null != vo.getMax()){
vo.setMax(handleRateNum(vo.getMax(),null,false));
}
if(null != vo.getAvg()){
vo.setAvg(handleRateNum(vo.getAvg(),null,false));
}
}
if(null != totalUnitIdRateBig && null != vo.getCou()){
vo.setCou(handleRateNum(vo.getCou(),totalUnitIdRateBig,true));
}
if(null == totalUnitIdRateBig && null != vo.getCou()){
//前端需处理成斜杠
vo.setCou(handleRateNum(vo.getCou(),null,false));
}
}
}
private void createCalcTask(List<?> list,int num){
int cou = 0;
if(list != null){
List<? extends List<?>> lists = rangeList(list, list.size() / num);
while (cou < lists.size()){
CalcTask calcTask = new CalcTask(deviceId,factorId, unitRateMap, cunit,
listMinDataShowVO != null ? (List<MinDataShowStrVO>)lists.get(cou) : null,
listHourDataShowVO != null ? (List<HourDataShowStrVO>)lists.get(cou) : null,
listDayDataShowVO != null ? (List<DayDataShowStrVO>)lists.get(cou) : null,
rateBig,totalUnitIdRateBig);
cou++;
calcTask.fork();
calcTask.join();
}
}
}
/**
* 平均分割集合 最后一个角标数据取余
* @param list 数据集合
* @param size []
* @param <T>
* @return
*/
private <T> List<List<T>> rangeList(List<T>list , int size){
if(ObjectUtils.isEmpty(list)){
return Collections.emptyList();
}
int block = (list.size() + size -1) / size;
return IntStream.range(0,block).
boxed().map(i->{
int start = i*size;
int end = Math.min(start + size,list.size());
return list.subList(start,end);
}).collect(Collectors.toList());
}
}
/**
* 返回保留几位小数
* @param str
* @return
*/
private static int countNum(String str){
int d = str.indexOf(".");
String str2 = str.substring(d+1,str.length());
String[] split = str2.split("");
int num = 2;
for (int a = 0;a < split.length;a++) {
if(!split[a].equals("0")){
if(a > 1){
num = a+2;
break;
}
}
}
return num;
}
/**
* 根据规则保留有效数字
* @param val 转换值
* @param val2 转换率
* @boolean calc 是否计算
* @return
*/
public static String handleRateNum(String val,BigDecimal val2,boolean calc){
if(val == null){
return null;
}
BigDecimal vall = new BigDecimal(val);
if(StringUtils.isNotBlank(val) && val2 != null && calc){
vall = vall.multiply(val2);
}
BigDecimal bigdecimal = null;
//小于0.01跟-0.01需要保留有效位数两位
if(vall.compareTo(new BigDecimal(0.01)) == -1){
bigdecimal = vall.setScale(countNum(vall.toPlainString()),BigDecimal.ROUND_HALF_UP);
}else{
//大于0.01需要保留两位小数
bigdecimal = vall.setScale(2,BigDecimal.ROUND_HALF_UP);
}
//如果保留的两位小数是00的话直接取整
if(bigdecimal.intValue() > 0 && String.valueOf(bigdecimal).contains("00")){
return String.valueOf(bigdecimal.setScale(0,BigDecimal.ROUND_UP));
}else{
return bigdecimal.stripTrailingZeros().toPlainString();
}
}
public static void main(String[] args) {
System.out.println(handleRateNum("2777.77777778",new BigDecimal(3.600000000000),true));
}
/**
*
* @param deviceId
* @param factorId
* @param unitRateMap 单位转换map
* @param cunit 自定义单位 基准单位map 累计值单位
* @param listMinDataShowVO
* @param listHourDataShowVO
* @param listDayDataShowVO
* @throws InterruptedException
*/
public static void CalcRate(String deviceId,String factorId, Map<String, BigDecimal> unitRateMap, Map<String, BxWellDeviceFactor> cunit,
List<MinDataShowStrVO> listMinDataShowVO, List<HourDataShowStrVO> listHourDataShowVO, List<DayDataShowStrVO> listDayDataShowVO) throws InterruptedException {
if(cunit.get(deviceId +"_"+ factorId) == null){
return;
}
//自定义单位
BigDecimal rateBig = unitRateMap.get(cunit.get(deviceId +"_"+ factorId).getCUnit());
//自定义累计单位
String totalUnitId = cunit.get(deviceId + "_" + factorId).getTotalUnitId();
String factorTotalUnitId = cunit.get(deviceId + "_" + factorId).getFactorTotalUnitId();
BigDecimal totalUnitIdRateBig = unitRateMap.get(totalUnitId + "_" + factorTotalUnitId);
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new TimeForkJoinPoolCalcRate().new CalcTask(deviceId,factorId, unitRateMap, cunit,
listMinDataShowVO,listHourDataShowVO,listDayDataShowVO,
rateBig,totalUnitIdRateBig));
pool.shutdown();
}
}