多執行緒使用之主執行緒與多執行緒響應同步
阿新 • • 發佈:2019-02-12
需求:
匯出資料10000條資料到excel表中。希望用多執行緒優化匯出速度。
設計:
將10000條資料分成10份。用10個執行緒分別給excel寫值分10個sheet頁問題:
匯出資料時,由於寫值啟動了多執行緒,匯出資料為空excel表格問題的梳理:
由於啟動了多執行緒,多執行緒的意義是不影響主執行緒的響應速度,這樣導致的問題是response響應給excel表了,但是值還沒有寫入,產生了空表解決方法:
當所有多執行緒執行完畢之後主執行緒再響應前臺
注意事項:此方法不可以使用可快取執行緒池去建立的多執行緒。這裡使用的可設定數量的多執行緒池
程式碼controller 這個類是提前獲取到輸出流,當方法執行完畢時自動響應前臺下載的檔案
@RequestMapping("/common/cuijiDownLoad") public void cuijiDownLoad(CaseHead caseHead, CuijiDownLoadEx cuijiDownLoadEx, HttpServletRequest request, HttpServletResponse response, HttpSession session, CaseHead head, CaseParamsExtend exParams, @RequestParam(value = "type", defaultValue = "-1") Integer type) { response.setCharacterEncoding("UTF-8");// 設定相應內容的編碼格式 // 設定匯出檔案檔名 String name = "案件催記"; name = name + fmt.format(new Date()); OutputStream os = null; String companycode = SessionUtil.getCompnayCodeFromSessionByCuishouAdminOrUser(session); try { String fname = name + ".xls"; fname = CompanyadminController.toUtf8String(request, fname); response.reset();// 清空輸出流 response.setHeader("Content-Disposition", "attachment;filename=" + fname); response.setContentType("application/msexcel");// 定義輸出型別 os = response.getOutputStream();// 取得輸出流 commonService.toExcelCuijiByCaseHead(os, caseHead, cuijiDownLoadEx);//方法 } catch (Exception e) { e.printStackTrace(); } finally { if (os != null) { try { os.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
這裡放入自定義等分list的方法
主要使用執行緒的方法:/** * 將一個list均分成n個list,主要通過偏移量來實現的 * @param maps * @return */ public static List<List<Map<String, Object>>> averageAssign(List<Map<String, Object>> maps,int n){ List<List<Map<String, Object>>> result=new ArrayList<List<Map<String, Object>>>(); int remaider=maps.size()%n; //(先計算出餘數) int number=maps.size()/n; //然後是商 int offset=0;//偏移量 for(int i=0;i<n;i++){ List<Map<String, Object>> value=null; if(remaider>0){ value=maps.subList(i*number+offset, (i+1)*number+offset+1); remaider--; offset++; }else{ value=maps.subList(i*number+offset, (i+1)*number+offset); } result.add(value); } return result; }
判斷執行緒完全執行完的程式碼
exec.shutdown();
while(true){
if(exec.isTerminated()){
writer.write("---END---\n");
writer.close();
System.out.println("所有的子執行緒都結束了!");
break;
}
Thread.sleep(1000);
}
exe.shutdown();該方法在加入執行緒佇列的執行緒執行完之前不會執行。exe.isTerminated();當shutdown()或者shutdownNow()執行了之後才會執行,並返回true。
在上面的程式碼中必須有exe.isTerminated()的判斷,否則在投入5個執行緒到執行緒池後會直接列印:“結束了”。不能達到我們想要的效果。
通過while(true)迴圈判斷exe.isTerminated()重生之大文豪的值,為了防止過多的判斷浪費資源,可設定執行緒睡眠Thread.sleep(200);
正是由於這個睡眠,所以當所有執行緒池中的執行緒都執行完後,有可能延遲200ms才執行"結束了"語句。這個引數越小延遲越小,結果越準確。
具體使用場景:
public void subMapListAndDownload(final OutputStream os,final String[] fieldHeadName, final String[] dataName,final List<Map<String, Object>> maps){
//將listmap分份
List<List<Map<String, Object>>> maplist=new ArrayList<List<Map<String, Object>>>();
if (maps.size() <1000){
//不分份
maplist.add(maps);
}else if (maps.size() >=1000 && maps.size() <2000 ){
//兩等分
maplist= averageAssign(maps,2);
}else if (maps.size() >=2000 && maps.size() <3000 ){
//三等分
maplist= averageAssign(maps,3);
}else if (maps.size() >=3000 && maps.size() <4000 ){
//四等分
maplist= averageAssign(maps,4);
}else if (maps.size() >=4000 && maps.size() <5000 ){
//五等分
maplist= averageAssign(maps,5);
}else if (maps.size() >=5000 && maps.size() <6000 ){
//六等分
maplist= averageAssign(maps,6);
}else if (maps.size() >=6000 && maps.size() <7000 ){
//七等分
maplist= averageAssign(maps,7);
}else if (maps.size() >=7000 && maps.size() <8000 ){
//八等分
maplist= averageAssign(maps,8);
}else if (maps.size() >=8000 && maps.size() <9000 ){
//九等分
maplist= averageAssign(maps,9);
}else if (maps.size() >=10000 ){
//十等分
maplist= averageAssign(maps,10);
}
// 建立一個webbook,對應一個Excel檔案
final HSSFWorkbook wb = new HSSFWorkbook();
//將分完份的按份數設定執行緒
ExecutorService cachedThreadPool = Executors.newFixedThreadPool(maplist.size()); //固定數量的執行緒池
for (int i = 0; i <maplist.size() ; i++) {
final List<Map<String, Object>> maps1=maplist.get(i);
//ExcelFile.writeExcelIncludeData(os, fieldHeadName, dataName, maps1);
cachedThreadPool.execute(new Runnable() {
public void run() {
if (os != null && fieldHeadName != null && dataName != null && maps1 != null)
ExcelFile.writeExcelIncludeDataSheet(os, fieldHeadName, dataName, maps1,wb);
}
});
}
boolean over=false;
cachedThreadPool.shutdown();
while (true) {
if (cachedThreadPool.isTerminated()) {
System.out.println("結束了!");
over=true;
if (over != true){
try {
Thread.sleep (3000) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
try {
wb.write(os);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
給excel新增sheet和值的方法
/**
* 匯出表頭和資料
*
* @param oupOutputStream
* @param fieldName
* @param mapList
*/
public static void writeExcelIncludeDataSheet(OutputStream oupOutputStream, String[] fieldName,String[] dataName,
List<Map<String, Object>> mapList, HSSFWorkbook wb ) {
// 在webbook中新增一個sheet,對應Excel檔案中的sheet
HSSFSheet s = wb.createSheet();
createTagAndData(fieldName,dataName, s,mapList);// 寫表格的頭部
// 建立單元格,並設定值表頭 設定表頭居中
HSSFCellStyle style = wb.createCellStyle();
style.setAlignment(HSSFCellStyle.ALIGN_CENTER); // 建立一個居中格
}
private static void createTagAndData(String[] tags,String[] dataName,HSSFSheet s,List<Map<String, Object>> mapList){
HSSFRow row = s.createRow(0);
HSSFCell cell = null;
for (int i = 0; i < tags.length; i++) {
cell = row.createCell(i);
cell.setCellValue(tags[i]);
}
for(int i=1;i<=mapList.size();i++){
row = s.createRow(i);
cell = null;
for (int s1 = 0; s1 < tags.length; s1++) {
cell = row.createCell(s1);
if(mapList.get(i-1).get(dataName[s1])!=null){
cell.setCellValue(mapList.get(i-1).get(dataName[s1]).toString());
}
}
}
}
多執行緒之路漫漫,還要不段摸索
感謝觀看