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