Fork-Join的介绍

  Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

我们再通过Fork和Join这两个单词来理解下Fork/Join框架,Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结果。比如计算1+2+。。+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和,最终汇总这10个子任务的结果。

工作窃取算法

  假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务。

Fork/Join框架

  • ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join()操作的机制,通常情况下我们不需要直接继承ForkJoinTask类,而只需要继承它的子类,Fork/Join框架提供了以下两个子类:
    • RecursiveAction:用于没有返回结果的任务。
    • RecursiveTask :用于有返回结果的任务。
  • ForkJoinPool :ForkJoinTask需要通过ForkJoinPool来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。  

以下实例为通过excel文件批量导入大量数据的实例:

Fork_Join实例

package forkjoin;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class ForkJoinService{

	private static ForkJoinService instance = null;
	private ForkJoinPool forkjoinpool;//线程池
	private int maxPoolSize;//最大线程数
	public int getMaxPoolSize() {
		return maxPoolSize;
	}
	public void setMaxPoolSize(int maxPoolSize) {
		this.maxPoolSize = maxPoolSize;
	}
	public ForkJoinService(){//获取本系统的有效线程数,设置线程池为有效线程的两倍。
		int size = Runtime.getRuntime().availableProcessors();
		maxPoolSize = size*2;
		forkjoinpool = new ForkJoinPool(maxPoolSize);
		instance = this;
	}
	private void stop(){
		if(forkjoinpool != null)
			forkjoinpool.shutdown();
	}
	public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task){//将任务提交给线程,去执行。
		return forkjoinpool.submit(task);
	}
	public <T> T invoke(ForkJoinTask<T> task){
		return forkjoinpool.invoke(task);
	}
}

  

public class PersonTask  extends RecursiveTask<List<JSONObject>> {

    private PersonService personService;
    private Sheet sheet = null;
    private int start = 0;
    private int end = 0;
    public PersonTask(Sheet sheet, int start, int end, PersonService personService){
        this.sheet = sheet;
        this.start = start;
        this.end = end;
        this.personService = personService;
    }
    @Override
    protected List<JSONObject> compute() {
        List<JSONObject> rtn = new LinkedList<>();
        if(sheet == null)
            return Collections.emptyList();
        if (end-start<=500){
            try {
                rtn = importExcel(sheet, start, end);
                List<Person> list = new LinkedList<>();
                for (int i=0; i<rtn.size(); i++){
                    Person person = new Person();
                    person = JSONObject.toJavaObject(rtn.get(i), Person.class);
                    list.add(person);
                }
                if (personService==null){
                    System.out.println("=====333333333333========person======isNull=====");
                }
                personService.savePersonList(list);
//                System.out.println("=====333333333333============="+start+"======"+end);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return rtn;
        }
        int middle = (end + start) / 2;
        PersonTask subtask1 = new PersonTask(this.sheet, start, middle, personService);//自己调用自己  递归
        PersonTask subtask2 = new PersonTask(this.sheet, middle, end, personService);//自己调用自己  递归
        // 执行子任务
        subtask1.fork();
        subtask2.fork();
        // 等待任务执行结束合并其结果
        List<JSONObject> leftResult = subtask1.join();
        List<JSONObject> rightResult = subtask1.join();
        rtn.addAll(leftResult);
        rtn.addAll(rightResult);
        return rtn;
    }

    /**
     * 描述:获取IO流中的数据,组装成List<List<Object>>对象
     * @param sheet
     * @return
     * @throws Exception
     */
    public List<JSONObject> importExcel(Sheet sheet, int start, int end){
        List<JSONObject> list = new ArrayList<JSONObject>();
        Row row = null;
        Cell cell = null;
        for (int j = start; j <= end; j++) {
            row = sheet.getRow(j);
            if(row==null||row.getFirstCellNum()==j){continue;}
                //遍历所有的列
                JSONObject jsonObject = new JSONObject();
                for (int y = row.getFirstCellNum(); y <row.getLastCellNum(); y++) {
                    cell = row.getCell(y);
                    if(cell!=null){
                        jsonObject.put("value"+String.valueOf(y),getCellValue(cell));
                    }
                }
                list.add(jsonObject);
            }
        return list;
    }



    /**
     * 描述:对表格中数值进行格式化
     * @param cell
     * @return
     */
    public Object getCellValue(Cell cell){
        //用String接收所有返回的值
        String value = null;
        DecimalFormat df = new DecimalFormat("0");  //格式化number String字符
        SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");  //日期格式化
        DecimalFormat df2 = new DecimalFormat("0.00");  //格式化数字

        switch (cell.getCellType()) {
            case STRING:  //String类型的数据  不同版本名字不同 Cell.CELL_TYPE_STRING下面同这个
                value =  cell.getStringCellValue();
                break;

            case NUMERIC:   //数值类型(取值用cell.getNumericCellValue() 或cell.getDateCellValue())
                if("General".equals(cell.getCellStyle().getDataFormatString())){
                    value = df.format(cell.getNumericCellValue());
                }else if(HSSFDateUtil.isCellDateFormatted(cell)){
                    value = sdf.format(HSSFDateUtil.getJavaDate(cell.getNumericCellValue()));
                }else{
                    value = df2.format(cell.getNumericCellValue());
                }
                break;

            case BOOLEAN:  //Boolean类型
                value = String.valueOf(cell.getBooleanCellValue());
                break;


            case FORMULA: //表达式类型
                value = String.valueOf(cell.getCellFormula());
                break;

            case ERROR: //异常类型 不知道何时算异常
                value=String.valueOf(cell.getErrorCellValue());
                break;

            case BLANK:  //空,不知道何时算空
                value = "";
                break;

            default:
                value = "";
                break;
        }
        if(value.equals("")||value==null){
            value = "";
        }
        if (cell == null) {
            return "";
        }
        return value;
    }

}

  

 

@Controller@Api(tags = "文件上传")
@RestController
@RequestMapping("/excel")
@Slf4j
public class DcpExcelController {

    public final static String excel2003L =".xls";    //2003- 版本的excel
    public final static String excel2007U =".xlsx";   //2007+ 版本的excel

    @Autowired
    private PersonService personService;

    /**
     * 导入
     * @param  request
     * @return 导入数据
     */
    @ApiOperation("导入")
    @PostMapping(value = "importExcel")
    public ResponseEntity importExcel (HttpServletRequest request){
        System.out.println("==========开始=======");
        List<JSONObject> strings = importExcelNew(request);
        return ResponseEntity.ok(strings.size());

    }

    /**
     *  导入
     * @param request currentUser
     */
    public List<JSONObject> importExcelNew(HttpServletRequest request) {
        //改为所有格式错误一起提示
        Long startTime = System.currentTimeMillis();
        List<JSONObject> list = new LinkedList<>();
        try{
            MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
            MultipartFile multipartFile = multipartRequest.getFile("file");
            list = importExcel(multipartFile, personService);
            if (CheckEmptyUtil.isEmpty(list)){
                throw  new Exception("请按照正确的导入模板导入数据");
            }
//            System.out.println("==========开始===444444===="+list.size());
//            for (int i=0; i<list.size(); i++){
//                System.out.println("=========="+i+"========"+list.get(i));
//            }
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("=========="+(System.currentTimeMillis()-startTime)+"ms");
        return list;
    }

    /**
     * 描述:获取IO流中的数据,组装成List<List<Object>>对象
     * @param file
     * @return
     * @throws Exception
     */
    public List<JSONObject> importExcel(MultipartFile file, PersonService personService){
        ForkJoinService forkJoinService = new ForkJoinService();
        List<JSONObject> list = null;
        //创建Excel工作薄
        try {
            Workbook work = getWorkbook(file);
            if(null == work){
                throw new Exception("创建Excel工作薄为空!");
            }
            Sheet sheet = null;
            list = new ArrayList<JSONObject>();
            sheet = work.getSheetAt(0);
            if(sheet==null){return null;}
            int end = sheet.getLastRowNum();

            //遍历当前sheet中的所有行
//            for(int k=2;k<size;k+=pagesize){
//                int end = k+pagesize;
                ForkJoinTask<List<JSONObject>> result = forkJoinService.submit(new PersonTask(sheet, 0, end, personService));
//                System.out.println("==========开始======="+k);
                list.addAll(result.get());
//            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 初始化excel工作簿
     * @param file
     * @return
     * @throws Exception
     */
    public Workbook getWorkbook(MultipartFile file) throws Exception{
        Workbook wb = null;
        String fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
        if(excel2003L.equals(fileType)){
            wb = new HSSFWorkbook(file.getInputStream());  //2003-
        }else if(excel2007U.equals(fileType)){
//            wb = new XSSFWorkbook(new FileInputStream(file));  //2007+
            wb = new XSSFWorkbook(file.getInputStream());  //2007+
        }else{
            throw new Exception("解析的文件格式有误!");
        }
        return wb;
    }

}