批量任务体现多线程的威力!
背景
對(duì)于多線程的理解不是非常深刻,工作中用到多線程代碼的機(jī)會(huì)也不多,前不久遇到了一個(gè)使用場(chǎng)景,通過(guò)編碼實(shí)現(xiàn)后對(duì)于多線程的理解和應(yīng)用有了更加深刻的理解。場(chǎng)景如下:現(xiàn)有給用戶發(fā)送產(chǎn)品調(diào)研的需求,運(yùn)營(yíng)的同事拿來(lái)了一個(gè)Excel文件,要求給Excel里面大約六萬(wàn)個(gè)手機(jī)號(hào)發(fā)送調(diào)研短信。
最簡(jiǎn)單的方法就是一個(gè)循環(huán)然后單線程順序發(fā)送,但是核心問(wèn)題在于,給短信運(yùn)營(yíng)商發(fā)短信的接口響應(yīng)時(shí)間較長(zhǎng),假設(shè)平均100ms的響應(yīng)時(shí)間,那么單線程發(fā)送的話需要6萬(wàn)*0.1秒=6000秒。顯然這個(gè)時(shí)間是不能接受的,運(yùn)營(yíng)商系統(tǒng)的發(fā)送接口我們是不能優(yōu)化的,只得增強(qiáng)自己的發(fā)送和處理能力才能盡快的完成任務(wù)。
批量發(fā)短信
讀取Excel中的信息
包依賴
工具類代碼,Maven中引入如下兩個(gè)包
<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;// 循環(huán)行Rowfor (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內(nèi)容** @param cell* @return*/ private static String getStringValueFromCell(XSSFCell cell) {// 單元格內(nèi)的時(shí)間格式SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");// 單元格內(nèi)的數(shù)字類型DecimalFormat decimalFormat = new DecimalFormat("#.#####");// 單元格默認(rèn)為空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) {// 日期轉(zhuǎn)為時(shí)間形式if (DateUtil.isCellDateFormatted(cell)) {double d = cell.getNumericCellValue();Date date = DateUtil.getJavaDate(d);cellValue = dateFormat.format(date);} else {// 其他轉(zhuǎn)為數(shù)字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; }模擬運(yùn)營(yíng)商發(fā)送短信的方法
/*** 外部接口耗時(shí)長(zhǎng),通過(guò)多線程增強(qiáng)** @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); }多線程發(fā)短信
簡(jiǎn)單的單線程發(fā)送
/*** 單線程發(fā)送** @param phoneList* @return*/private long singleThread(List<String> phoneList) {long start = System.currentTimeMillis();/*// 直接主線程執(zhí)行for (String phoneNumber : phoneList) {threadOperation.sendMsgToPhone(phoneNumber);}*/SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(phoneList);smet.start();long totalTime = System.currentTimeMillis() - start;System.out.println("單線程發(fā)送總時(shí)間:" + totalTime);return totalTime;}對(duì)于大批量發(fā)短信的場(chǎng)景,如果使用單線程將全部一千個(gè)號(hào)碼發(fā)送完畢的話,大約需要103132ms,可見效率低下,耗費(fèi)時(shí)間較長(zhǎng)。
多線程發(fā)送短信中的一個(gè)核心要點(diǎn)是,將全部手機(jī)號(hào)碼拆分成多個(gè)組后,分配給每個(gè)線程進(jìn)行執(zhí)行。
兩個(gè)線程的示例
/*** 兩個(gè)線程發(fā)送** @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; }另一種數(shù)據(jù)分組方式
/*** 另外一種分配方式** @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;}} }線程池發(fā)送
/*** 線程池發(fā)送** @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發(fā)送
/*** 多線程發(fā)送** @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(); }使用多線程發(fā)送,將發(fā)送任務(wù)進(jìn)行分割然后分配給每個(gè)線程執(zhí)行,執(zhí)行完畢需要10266ms,可見執(zhí)行效率明顯提升,消耗時(shí)間明顯縮短。
完整代碼
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 {// 發(fā)短信的同步等待時(shí)間private static final long SEND_COST_TIME = 100L;// 手機(jī)號(hào)文件private static final String FILE_NAME = "/Users/lingye/Downloads/phone_number.xlsx";// 手機(jī)號(hào)列表private static List<String> phoneList = new ArrayList<>();// 單例對(duì)象private static volatile ThreadOperation threadOperation;// 線程個(gè)數(shù)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() {// 從本地文件中讀取手機(jī)號(hào)碼readFromExcel(FILE_NAME);}public static void main(String[] args) {ThreadOperation threadOperation = getInstance();//threadOperation.singleThread(phoneList);threadOperation.multiThreadSend(phoneList);}/*** 單例獲取對(duì)象** @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;// 循環(huán)行Rowfor (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內(nèi)容** @param cell* @return*/private static String getStringValueFromCell(XSSFCell cell) {// 單元格內(nèi)的時(shí)間格式SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");// 單元格內(nèi)的數(shù)字類型DecimalFormat decimalFormat = new DecimalFormat("#.#####");// 單元格默認(rèn)為空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) {// 日期轉(zhuǎn)為時(shí)間形式if (DateUtil.isCellDateFormatted(cell)) {double d = cell.getNumericCellValue();Date date = DateUtil.getJavaDate(d);cellValue = dateFormat.format(date);} else {// 其他轉(zhuǎn)為數(shù)字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;}/*** 外部接口耗時(shí)長(zhǎng),通過(guò)多線程增強(qiáng)** @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);}/*** 單線程發(fā)送** @param phoneList* @return*/private long singleThread(List<String> phoneList) {long start = System.currentTimeMillis();/*// 直接主線程執(zhí)行for (String phoneNumber : phoneList) {threadOperation.sendMsgToPhone(phoneNumber);}*/SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(phoneList);smet.start();long totalTime = System.currentTimeMillis() - start;System.out.println("單線程發(fā)送總時(shí)間:" + 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;}}}/*** 兩個(gè)線程發(fā)送** @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;}/*** 線程池發(fā)送** @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();}/*** 多線程發(fā)送** @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;}@Overridepublic 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;}@Overridepublic Long call() throws Exception {Long startMills = System.currentTimeMillis();for (String number : numberListByThread) {sendMsgToPhone(number);}Long endMills = System.currentTimeMillis();return endMills - startMills;}} }總結(jié)
以上是生活随笔為你收集整理的批量任务体现多线程的威力!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: SAP S/4HANA销售订单创建时,会
- 下一篇: 离用户近一点,再近一点