通過批量發短信實例來認識多線程的威力!
阿新 • • 發佈:2019-02-05
extends ads exce lld vol nds art 自己的 批量
讀取Excel的工具類代碼
背景
對於多線程的理解不是非常深刻,工作中用到多線程代碼的機會也不多,前不久遇到了一個使用場景,通過編碼實現後對於多線程的理解和應用有了更加深刻的理解。場景如下:現有給用戶發送產品調研的需求,運營的同事拿來了一個Excel文件,要求給Excel裏面大約六萬個手機號發送調研短信。
最簡單的方法就是一個循環然後單線程順序發送,但是核心問題在於,給短信運營商發短信的接口響應時間較長,假設平均100ms的響應時間,那麽單線程發送的話需要6萬*0.1秒=6000秒。顯然這個時間是不能接受的,運營商系統的發送接口我們是不能優化的,只得增強自己的發送和處理能力才能盡快的完成任務。
讀取Excel中的信息
工具類代碼,Maven中引入如下兩個包
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency> <dependency> <groupId>org.apache.xmlbeans</groupId> <artifactId>xmlbeans</artifactId> <version>2.6.0</version> </dependency>
讀取Excel的工具類代碼
/** * 讀取Excel的文件信息 * * @param fileName */ public static void readFromExcel(String fileName) { InputStream is = null; try { is = new FileInputStream(fileName); XSSFWorkbook workbook = new XSSFWorkbook(is); XSSFSheet sheet = workbook.getSheetAt(0); int num = 0; // 循環行Row for (int rowNum = 0, lastNum = sheet.getLastRowNum(); rowNum <= lastNum; rowNum++) { XSSFRow row = sheet.getRow(rowNum); String phoneNumber = getStringValueFromCell(row.getCell(0)).trim(); phoneList.add(phoneNumber); } System.out.println(num); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 讀取Excel裏面Cell內容 * * @param cell * @return */ private static String getStringValueFromCell(XSSFCell cell) { // 單元格內的時間格式 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // 單元格內的數字類型 DecimalFormat decimalFormat = new DecimalFormat("#.#####"); // 單元格默認為空 String cellValue = ""; if (cell == null) { return cellValue; } // 按類型讀取 if (cell.getCellType() == XSSFCell.CELL_TYPE_STRING) { cellValue = cell.getStringCellValue(); } else if (cell.getCellType() == XSSFCell.CELL_TYPE_NUMERIC) { // 日期轉為時間形式 if (DateUtil.isCellDateFormatted(cell)) { double d = cell.getNumericCellValue(); Date date = DateUtil.getJavaDate(d); cellValue = dateFormat.format(date); } else { // 其他轉為數字 cellValue = decimalFormat.format((cell.getNumericCellValue())); } } else if (cell.getCellType() == XSSFCell.CELL_TYPE_BLANK) { cellValue = ""; } else if (cell.getCellType() == XSSFCell.CELL_TYPE_BOOLEAN) { cellValue = String.valueOf(cell.getBooleanCellValue()); } else if (cell.getCellType() == XSSFCell.CELL_TYPE_ERROR) { cellValue = ""; } else if (cell.getCellType() == XSSFCell.CELL_TYPE_FORMULA) { cellValue = cell.getCellFormula().toString(); } return cellValue; }
模擬運營商發送短信的方法
/** * 外部接口耗時長,通過多線程增強 * * @param userPhone */ public void sendMsgToPhone(String userPhone) { try { Thread.sleep(SEND_COST_TIME); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("send message to : " + userPhone); }
多線程
簡單的單線程發送
/** * 單線程發送 * * @param phoneList * @return */ private long singleThread(List<String> phoneList) { long start = System.currentTimeMillis(); /*// 直接主線程執行 for (String phoneNumber : phoneList) { threadOperation.sendMsgToPhone(phoneNumber); }*/ SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(phoneList); smet.start(); long totalTime = System.currentTimeMillis() - start; System.out.println("單線程發送總時間:" + totalTime); return totalTime; }
對於大批量發短信的場景,如果使用單線程將全部一千個號碼發送完畢的話,大約需要103132ms,可見效率低下,耗費時間較長。
多線程發送短信中的一個核心要點是,將全部手機號碼拆分成多個組後,分配給每個線程進行執行。
兩個線程的示例
/** * 兩個線程發送 * * @param phoneList * @return */ private long twoThreads(List<String> phoneList) { long start = System.currentTimeMillis(); List<String> list1 = phoneList.subList(0, phoneList.size() / 2); List<String> list2 = phoneList.subList(phoneList.size() / 2, phoneList.size()); SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(list1); smet.start(); SendMsgExtendThread smet1 = threadOperation.new SendMsgExtendThread(list2); smet1.start(); return 0; }
另一種方便的分配方式
/** * 另外一種分配方式 * * @param phoneList */ private void otherThread(List<String> phoneList) { for (int threadNo = 0; threadNo < 10; threadNo++) { int numbersPerThread = 10; List<String> list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 10); SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(list); smet.start(); if (list.size() < numbersPerThread) { break; } } }
線程池發送
/** * 線程池發送 * * @param phoneList * @return */ private void threadPool(List<String> phoneList) { for (int threadNo = 0; threadNo < THREAD_POOL_SIZE; threadNo++) { int numbersPerThread = 10; List<String> list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 10); threadOperation.executorService.execute(threadOperation.new SendMsgExtendThread(list)); } threadOperation.executorService.shutdown(); }
使用Callable發送
/** * 多線程發送 * * @param phoneList * @return */ private void multiThreadSend(List<String> phoneList) { List<Future<Long>> futures = new ArrayList<>(); for (int threadNo = 0; threadNo < THREAD_POOL_SIZE; threadNo++) { int numbersPerThread = 100; List<String> list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 100); Future<Long> future = threadOperation.executorService.submit(threadOperation.new SendMsgImplCallable(list, String.valueOf(threadNo))); futures.add(future); } for (Future<Long> future : futures) { try { System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } threadOperation.executorService.shutdown(); }
使用多線程發送,將發送任務進行分割然後分配給每個線程執行,執行完畢需要10266ms,可見執行效率明顯提升,消耗時間明顯縮短。
完整代碼
package com.lingyejun.tick.authenticator; import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.*; public class ThreadOperation { // 發短信的同步等待時間 private static final long SEND_COST_TIME = 100L; // 手機號文件 private static final String FILE_NAME = "/Users/lingye/Downloads/phone_number.xlsx"; // 手機號列表 private static List<String> phoneList = new ArrayList<>(); // 單例對象 private static volatile ThreadOperation threadOperation; // 線程個數 private static final int THREAD_POOL_SIZE = 10; // 初始化線程池 private ExecutorService executorService = new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); public ThreadOperation() { // 從本地文件中讀取手機號碼 readFromExcel(FILE_NAME); } public static void main(String[] args) { ThreadOperation threadOperation = getInstance(); //threadOperation.singleThread(phoneList); threadOperation.multiThreadSend(phoneList); } /** * 單例獲取對象 * * @return */ public static ThreadOperation getInstance() { if (threadOperation == null) { synchronized (ThreadOperation.class) { if (threadOperation == null) { threadOperation = new ThreadOperation(); } } } return threadOperation; } /** * 讀取Excel的文件信息 * * @param fileName */ public static void readFromExcel(String fileName) { InputStream is = null; try { is = new FileInputStream(fileName); XSSFWorkbook workbook = new XSSFWorkbook(is); XSSFSheet sheet = workbook.getSheetAt(0); int num = 0; // 循環行Row for (int rowNum = 0, lastNum = sheet.getLastRowNum(); rowNum <= lastNum; rowNum++) { XSSFRow row = sheet.getRow(rowNum); String phoneNumber = getStringValueFromCell(row.getCell(0)).trim(); phoneList.add(phoneNumber); } System.out.println(num); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 讀取Excel裏面Cell內容 * * @param cell * @return */ private static String getStringValueFromCell(XSSFCell cell) { // 單元格內的時間格式 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // 單元格內的數字類型 DecimalFormat decimalFormat = new DecimalFormat("#.#####"); // 單元格默認為空 String cellValue = ""; if (cell == null) { return cellValue; } // 按類型讀取 if (cell.getCellType() == XSSFCell.CELL_TYPE_STRING) { cellValue = cell.getStringCellValue(); } else if (cell.getCellType() == XSSFCell.CELL_TYPE_NUMERIC) { // 日期轉為時間形式 if (DateUtil.isCellDateFormatted(cell)) { double d = cell.getNumericCellValue(); Date date = DateUtil.getJavaDate(d); cellValue = dateFormat.format(date); } else { // 其他轉為數字 cellValue = decimalFormat.format((cell.getNumericCellValue())); } } else if (cell.getCellType() == XSSFCell.CELL_TYPE_BLANK) { cellValue = ""; } else if (cell.getCellType() == XSSFCell.CELL_TYPE_BOOLEAN) { cellValue = String.valueOf(cell.getBooleanCellValue()); } else if (cell.getCellType() == XSSFCell.CELL_TYPE_ERROR) { cellValue = ""; } else if (cell.getCellType() == XSSFCell.CELL_TYPE_FORMULA) { cellValue = cell.getCellFormula().toString(); } return cellValue; } /** * 外部接口耗時長,通過多線程增強 * * @param userPhone */ public void sendMsgToPhone(String userPhone) { try { Thread.sleep(SEND_COST_TIME); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("send message to : " + userPhone); } /** * 單線程發送 * * @param phoneList * @return */ private long singleThread(List<String> phoneList) { long start = System.currentTimeMillis(); /*// 直接主線程執行 for (String phoneNumber : phoneList) { threadOperation.sendMsgToPhone(phoneNumber); }*/ SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(phoneList); smet.start(); long totalTime = System.currentTimeMillis() - start; System.out.println("單線程發送總時間:" + totalTime); return totalTime; } /** * 另外一種分配方式 * * @param phoneList */ private void otherThread(List<String> phoneList) { for (int threadNo = 0; threadNo < 10; threadNo++) { int numbersPerThread = 10; List<String> list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 10); SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(list); smet.start(); if (list.size() < numbersPerThread) { break; } } } /** * 兩個線程發送 * * @param phoneList * @return */ private long twoThreads(List<String> phoneList) { long start = System.currentTimeMillis(); List<String> list1 = phoneList.subList(0, phoneList.size() / 2); List<String> list2 = phoneList.subList(phoneList.size() / 2, phoneList.size()); SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(list1); smet.start(); SendMsgExtendThread smet1 = threadOperation.new SendMsgExtendThread(list2); smet1.start(); return 0; } /** * 線程池發送 * * @param phoneList * @return */ private void threadPool(List<String> phoneList) { for (int threadNo = 0; threadNo < THREAD_POOL_SIZE; threadNo++) { int numbersPerThread = 10; List<String> list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 10); threadOperation.executorService.execute(threadOperation.new SendMsgExtendThread(list)); } threadOperation.executorService.shutdown(); } /** * 多線程發送 * * @param phoneList * @return */ private void multiThreadSend(List<String> phoneList) { List<Future<Long>> futures = new ArrayList<>(); for (int threadNo = 0; threadNo < THREAD_POOL_SIZE; threadNo++) { int numbersPerThread = 100; List<String> list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 100); Future<Long> future = threadOperation.executorService.submit(threadOperation.new SendMsgImplCallable(list, String.valueOf(threadNo))); futures.add(future); } for (Future<Long> future : futures) { try { System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } threadOperation.executorService.shutdown(); } public class SendMsgExtendThread extends Thread { private List<String> numberListByThread; public SendMsgExtendThread(List<String> numberList) { numberListByThread = numberList; } @Override public void run() { long startTime = System.currentTimeMillis(); for (int i = 0; i < numberListByThread.size(); i++) { System.out.print("no." + (i + 1)); sendMsgToPhone(numberListByThread.get(i)); } System.out.println("== single thread send " + numberListByThread.size() + "execute time:" + (System.currentTimeMillis() - startTime) + " ms"); } } public class SendMsgImplCallable implements Callable<Long> { private List<String> numberListByThread; private String threadName; public SendMsgImplCallable(List<String> numberList, String threadName) { numberListByThread = numberList; this.threadName = threadName; } @Override public Long call() throws Exception { Long startMills = System.currentTimeMillis(); for (String number : numberListByThread) { sendMsgToPhone(number); } Long endMills = System.currentTimeMillis(); return endMills - startMills; } } }
通過批量發短信實例來認識多線程的威力!