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; } }