1.概述java
最近工做中,有須要使用Fork/join 優化 的業務,本文我已實際案例出發,代碼落地的形式來實現優化。數據庫
Fork/Join 是什麼?apache
Fork/Join框架是一個實現了ExecutorService接口的多線程處理器。它能夠把一個大的任務劃分爲若干個小的任務併發執行,充分利用可用的資源,進而提升應用的執行效率。bash
2. 業務邏輯多線程
後臺Excel批量導入學生,導入的時候須要與數據庫中的數據進行比較,判斷username是否重複,若是重複則不導入。(ps:實際業務中多是其餘數據進行去重,邏輯是同樣的,本文中用學生數據做爲案例)併發
3.已知app
數據庫 student表 10w條數據框架
excel中 1w條數據dom
3.思路ide
3.1 初版本思路
爲了減小數據庫的壓力,咱們
第一步把全部數據讀取到Java內存中
第二步 excel 數據 自己去重
第三步 遍歷excel數據,每個username拿去與dbData判斷是否有相等的
在 數據庫10w excel 1w 條的數據狀況下 篩選耗時 8049ms
3.2 第二步思路(加入Fork/join)
結合業務,個人思路是選擇在上述的第三步優化,把以前的 1w條數據篩選 拆分成2個 5000的任務 同時進行
這樣在其餘因素不變的狀況下 ,第三步篩選的效率會提升大概一倍
實現
第一步 編寫一個有返回值的任務類,定義好
THRESHOLD_NUM (單個線程處理數據個數)
start ,end (下標)
第二步 在 compute 實現 裏面實現邏輯與任務的拆分
當處理數據小於等於 單個線程處理的數據值 ,則進行去重的業務
當處理數據小於等於 單個線程處理的數據值 ,則須要對任務的拆分與結果集的合併(ps:遞歸調用)
第三步業務層調用
在 數據庫10w excel 1w 條的數據狀況下 篩選耗時 4319ms
效率提升接近一倍
4.代碼
package com.itbbs.service;
import com.itbbs.ArrayListUtil;
import com.itbbs.DataUtil;
import com.itbbs.pojo.Student;
import com.itbbs.task.DistinctTask;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
/**
* 學生操做service
* tjx
*/
public class StudentService {
/**
* 批量導入學生
*/
public void importStudent(List<Student> dbData,List<Student> excelData){
// excel自身去重
excelData = excelData.stream()
.filter(ArrayListUtil.distinctByKey(Student::getUsername)) //根據username 去重
.collect(Collectors.toList());
//不重複數據
List<Student> repetitionData = new ArrayList<>();
long s = System.currentTimeMillis();
//遍歷因此excel數據
for (Student data : excelData) {
String username = data.getUsername();
//判斷是否存在於 dbData中
if (!ArrayListUtil.isInclude(dbData, username)) {
//若是不存在則添加到不重複集合中
repetitionData.add(data);
}
}
long e = System.currentTimeMillis();
System.out.println("篩選耗時:"+(e-s)+"ms");
//在數據庫不重複的數據裏面 篩選出不重複數據
repetitionData =repetitionData.stream()
.sorted(Comparator.comparing(Student::getUsername))//根據username 排序
.collect(Collectors.toList());
// repetitionData.forEach(p-> System.out.println(p.getUsername()));
}
/**
* 批量導入學生
*/
public void importStudent2(List<Student> dbData,List<Student> excelData){
// 自身去重
excelData = excelData.stream()
.filter(ArrayListUtil.distinctByKey(Student::getUsername)) //根據username 去重
.collect(Collectors.toList());
long s = System.currentTimeMillis();
//獲取不重複數據
ForkJoinPool fjp = new ForkJoinPool();
DistinctTask task = new DistinctTask(0,excelData.size(),dbData,excelData);
List<Student> repetitionData = fjp.invoke(task);
long e = System.currentTimeMillis();
System.out.println("篩選耗時:"+(e-s)+"ms");
//在數據庫不重複的數據裏面 篩選出不重複數據
repetitionData =repetitionData.stream()
.sorted(Comparator.comparing(Student::getUsername))//根據username 排序
.collect(Collectors.toList());
// repetitionData.forEach(p-> System.out.println(p.getUsername()));
}
public static void main(String[] args) {
// 模擬獲取數據庫
List<Student> dbData = DataUtil.getDbData();
// 模擬獲取excel數據
List<Student> excelData = DataUtil.getExcelData();
new StudentService().importStudent(dbData,excelData);
new StudentService().importStudent2(dbData,excelData);
}
}
複製代碼
package com.itbbs.task;
import com.itbbs.ArrayListUtil;
import com.itbbs.pojo.Student;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;
public class DistinctTask extends RecursiveTask<List<Student>> {
//單個任務處理數據
private static final int THRESHOLD_NUM = 5000;
//下標
private int start, end;
//須要處理的數據
private List<Student> dbData;
private List<Student> excelData;
public DistinctTask(int start, int end, List<Student> dbData, List<Student> excelData) {
this.start = start;
this.end = end;
this.dbData = dbData;
this.excelData = excelData;
}
@Override
protected List<Student> compute() {
//獲取當前下標下的數據
excelData = excelData.subList(start,end);
//獲取須要計算的數據量
int size = excelData.size();
if(size<=THRESHOLD_NUM){
//計算
List<Student> repetitionData = new ArrayList<>();
//遍歷因此excel數據
for (Student data : excelData) {
String username = data.getUsername();
//判斷是否存在於 dbData中
if (!ArrayListUtil.isInclude(dbData, username)) {
//若是不存在則添加到不重複集合中
repetitionData.add(data);
}
}
return repetitionData;
}else{
//拆分
int middle = (start + end) / 2;
DistinctTask left = new DistinctTask(start,middle,dbData,excelData);
DistinctTask right = new DistinctTask(middle+1,end,dbData,excelData);
//執行子任務
left.fork();
right.fork();
//獲取子任務結果
//join() 方法會阻塞到結果算出來
List<Student> lResult = left.join();
List<Student> rResult = right.join();
//何並結果
lResult.addAll(rResult);
return lResult;
}
}
}
複製代碼
package com.itbbs;
import com.itbbs.pojo.Student;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 數據源 工具類
* tjx
*/
public class DataUtil {
/**
* 模擬 獲取數據庫
* @return
*/
public static List<Student> getDbData(){
List<Student> result = new ArrayList<Student>();
Random random = new Random();
for (int i = 0; i <100000 ; i++) {
Student student = new Student();
student.setUsername(random.nextInt(99)+"");
result.add(student);
}
return result;
}
/**
* 模擬 獲取excel數據
* @return
*/
public static List<Student> getExcelData(){
List<Student> result = new ArrayList<Student>();
Random random = new Random();
for (int i = 0; i <10000 ; i++) {
Student student = new Student();
student.setUsername(random.nextInt(100000)+"");
result.add(student);
}
return result;
}
}
複製代碼
package com.itbbs;
import com.itbbs.pojo.Student;
import org.apache.commons.lang3.ArrayUtils;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
public class ArrayListUtil {
/** * 去重複元素 * @param keyExtractor * @param <T> * @return */ public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
Map<Object, Boolean> map = new ConcurrentHashMap<>(); return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
/**
* 判斷 list 是否包含 targetValue
* @param list
* @param targetValue
* @return
*/
public static boolean isInclude(List<Student> list, String targetValue){
return ArrayUtils.contains(list.toArray(),targetValue);
}
}
複製代碼
package com.itbbs.pojo;
public class Student {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Student) {
Student student = (Student) obj;
return (username.equals(student.username));
}
return super.equals(obj);
}
@Override
public int hashCode() {
Student student = (Student) this;
return student.username.hashCode();
}
}
複製代碼