摘要:背景對(duì)于多線(xiàn)程的理解不是非常深刻,工作中用到多線(xiàn)程代碼的機(jī)會(huì)也不多,前不久遇到了一個(gè)使用場(chǎng)景,通過(guò)編碼實(shí)現(xiàn)后對(duì)于多線(xiàn)程的理解和應(yīng)用有了更加深刻的理解。多線(xiàn)程發(fā)送短信中的一個(gè)核心要點(diǎn)是,將全部手機(jī)號(hào)碼拆分成多個(gè)組后,分配給每個(gè)線(xiàn)程進(jìn)行執(zhí)行。
背景
對(duì)于多線(xiàn)程的理解不是非常深刻,工作中用到多線(xiàn)程代碼的機(jī)會(huì)也不多,前不久遇到了一個(gè)使用場(chǎng)景,通過(guò)編碼實(shí)現(xiàn)后對(duì)于多線(xiàn)程的理解和應(yīng)用有了更加深刻的理解。場(chǎng)景如下:現(xiàn)有給用戶(hù)發(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)然后單線(xiàn)程順序發(fā)送,但是核心問(wèn)題在于,給短信運(yùn)營(yíng)商發(fā)短信的接口響應(yīng)時(shí)間較長(zhǎng),假設(shè)平均100ms的響應(yīng)時(shí)間,那么單線(xiàn)程發(fā)送的話(huà)需要6萬(wàn)*0.1秒=6000秒。顯然這個(gè)時(shí)間是不能接受的,運(yùn)營(yíng)商系統(tǒng)的發(fā)送接口我們是不能優(yōu)化的,只得增強(qiáng)自己的發(fā)送和處理能力才能盡快的完成任務(wù)。
批量發(fā)短信 讀取Excel中的信息 包依賴(lài)工具類(lèi)代碼,Maven中引入如下兩個(gè)包
讀取Excel的工具類(lèi)代碼org.apache.poi poi-ooxml 3.17 org.apache.xmlbeans xmlbeans 2.6.0
/** * 讀取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)行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內(nèi)容 * * @param cell * @return */ private static String getStringValueFromCell(XSSFCell cell) { // 單元格內(nèi)的時(shí)間格式 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // 單元格內(nèi)的數(shù)字類(lèi)型 DecimalFormat decimalFormat = new DecimalFormat("#.#####"); // 單元格默認(rèn)為空 String cellValue = ""; if (cell == null) { return cellValue; } // 按類(lèi)型讀取 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ò)多線(xiàn)程增強(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); }多線(xiàn)程發(fā)短信 簡(jiǎn)單的單線(xiàn)程發(fā)送
/** * 單線(xiàn)程發(fā)送 * * @param phoneList * @return */ private long singleThread(ListphoneList) { long start = System.currentTimeMillis(); /*// 直接主線(xiàn)程執(zhí)行 for (String phoneNumber : phoneList) { threadOperation.sendMsgToPhone(phoneNumber); }*/ SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(phoneList); smet.start(); long totalTime = System.currentTimeMillis() - start; System.out.println("單線(xiàn)程發(fā)送總時(shí)間:" + totalTime); return totalTime; }
對(duì)于大批量發(fā)短信的場(chǎng)景,如果使用單線(xiàn)程將全部一千個(gè)號(hào)碼發(fā)送完畢的話(huà),大約需要103132ms,可見(jiàn)效率低下,耗費(fèi)時(shí)間較長(zhǎng)。
多線(xiàn)程發(fā)送短信中的一個(gè)核心要點(diǎn)是,將全部手機(jī)號(hào)碼拆分成多個(gè)組后,分配給每個(gè)線(xiàn)程進(jìn)行執(zhí)行。
兩個(gè)線(xiàn)程的示例/** * 兩個(gè)線(xiàn)程發(fā)送 * * @param phoneList * @return */ private long twoThreads(List另一種數(shù)據(jù)分組方式phoneList) { long start = System.currentTimeMillis(); List list1 = phoneList.subList(0, phoneList.size() / 2); List 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線(xiàn)程池發(fā)送phoneList) { for (int threadNo = 0; threadNo < 10; threadNo++) { int numbersPerThread = 10; List list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 10); SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(list); smet.start(); if (list.size() < numbersPerThread) { break; } } }
/** * 線(xiàn)程池發(fā)送 * * @param phoneList * @return */ private void threadPool(List使用Callable發(fā)送phoneList) { for (int threadNo = 0; threadNo < THREAD_POOL_SIZE; threadNo++) { int numbersPerThread = 10; List list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 10); threadOperation.executorService.execute(threadOperation.new SendMsgExtendThread(list)); } threadOperation.executorService.shutdown(); }
/** * 多線(xiàn)程發(fā)送 * * @param phoneList * @return */ private void multiThreadSend(ListphoneList) { List > futures = new ArrayList<>(); for (int threadNo = 0; threadNo < THREAD_POOL_SIZE; threadNo++) { int numbersPerThread = 100; List list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 100); Future future = threadOperation.executorService.submit(threadOperation.new SendMsgImplCallable(list, String.valueOf(threadNo))); futures.add(future); } for (Future future : futures) { try { System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } threadOperation.executorService.shutdown(); }
使用多線(xiàn)程發(fā)送,將發(fā)送任務(wù)進(jìn)行分割然后分配給每個(gè)線(xiàn)程執(zhí)行,執(zhí)行完畢需要10266ms,可見(jiàn)執(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 ListphoneList = new ArrayList<>(); // 單例對(duì)象 private static volatile ThreadOperation threadOperation; // 線(xiàn)程個(gè)數(shù) private static final int THREAD_POOL_SIZE = 10; // 初始化線(xiàn)程池 private ExecutorService executorService = new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue ()); 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)行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內(nèi)容 * * @param cell * @return */ private static String getStringValueFromCell(XSSFCell cell) { // 單元格內(nèi)的時(shí)間格式 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // 單元格內(nèi)的數(shù)字類(lèi)型 DecimalFormat decimalFormat = new DecimalFormat("#.#####"); // 單元格默認(rèn)為空 String cellValue = ""; if (cell == null) { return cellValue; } // 按類(lèi)型讀取 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ò)多線(xiàn)程增強(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); } /** * 單線(xiàn)程發(fā)送 * * @param phoneList * @return */ private long singleThread(List phoneList) { long start = System.currentTimeMillis(); /*// 直接主線(xiàn)程執(zhí)行 for (String phoneNumber : phoneList) { threadOperation.sendMsgToPhone(phoneNumber); }*/ SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(phoneList); smet.start(); long totalTime = System.currentTimeMillis() - start; System.out.println("單線(xiàn)程發(fā)送總時(shí)間:" + totalTime); return totalTime; } /** * 另外一種分配方式 * * @param phoneList */ private void otherThread(List phoneList) { for (int threadNo = 0; threadNo < 10; threadNo++) { int numbersPerThread = 10; List list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 10); SendMsgExtendThread smet = threadOperation.new SendMsgExtendThread(list); smet.start(); if (list.size() < numbersPerThread) { break; } } } /** * 兩個(gè)線(xiàn)程發(fā)送 * * @param phoneList * @return */ private long twoThreads(List phoneList) { long start = System.currentTimeMillis(); List list1 = phoneList.subList(0, phoneList.size() / 2); List 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; } /** * 線(xiàn)程池發(fā)送 * * @param phoneList * @return */ private void threadPool(List phoneList) { for (int threadNo = 0; threadNo < THREAD_POOL_SIZE; threadNo++) { int numbersPerThread = 10; List list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 10); threadOperation.executorService.execute(threadOperation.new SendMsgExtendThread(list)); } threadOperation.executorService.shutdown(); } /** * 多線(xiàn)程發(fā)送 * * @param phoneList * @return */ private void multiThreadSend(List phoneList) { List > futures = new ArrayList<>(); for (int threadNo = 0; threadNo < THREAD_POOL_SIZE; threadNo++) { int numbersPerThread = 100; List list = phoneList.subList(threadNo * numbersPerThread, (threadNo * numbersPerThread) + 100); Future future = threadOperation.executorService.submit(threadOperation.new SendMsgImplCallable(list, String.valueOf(threadNo))); futures.add(future); } for (Future 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 numberListByThread; public SendMsgExtendThread(List 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 { private List numberListByThread; private String threadName; public SendMsgImplCallable(List 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; } } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/77405.html
摘要:從多線(xiàn)程的發(fā)展來(lái)看,可以操作系統(tǒng)的發(fā)展分為三個(gè)歷史階段真空管和穿孔卡片晶體管和批處理系統(tǒng)集成電路和多道程序設(shè)計(jì)最早的計(jì)算機(jī)只能解決簡(jiǎn)單的數(shù)學(xué)運(yùn)算問(wèn)題,比如正弦余弦等。我們用了比較長(zhǎng)的篇幅介紹了進(jìn)程線(xiàn)程發(fā)展的歷史。 專(zhuān)題簡(jiǎn)介 作為一個(gè)合格的Java程序員,必須要對(duì)并發(fā)編程有一個(gè)深層次的了解,在很多互聯(lián)網(wǎng)企業(yè)都會(huì)重點(diǎn)考察這一塊??赡芎芏喙ぷ?年以上的Java程序員對(duì)于這一領(lǐng)域幾乎沒(méi)有太多研...
摘要:探測(cè)端口開(kāi)放原理就是向目標(biāo)發(fā)送請(qǐng)求,看是否有回應(yīng)。端口判定為不通。掃描批量目標(biāo)掃描批量目標(biāo)使用的并發(fā)隊(duì)列功能,去執(zhí)行的執(zhí)行單個(gè)任務(wù),在掃描前做了一些額外的工作把瀏覽器屏蔽的端口過(guò)濾掉了,收到的狀態(tài)就是。 0×00 前言 前兩天 freebuf上的的XSS到內(nèi)網(wǎng)的公開(kāi)課很受啟發(fā),從一個(gè)頁(yè)面到局域網(wǎng),威力著實(shí)增強(qiáng)不少 公開(kāi)課上檢測(cè)內(nèi)網(wǎng) IP 實(shí)現(xiàn)方式用的是 img 標(biāo)簽,加載網(wǎng)站的 fav...
摘要:探測(cè)端口開(kāi)放原理就是向目標(biāo)發(fā)送請(qǐng)求,看是否有回應(yīng)。端口判定為不通。掃描批量目標(biāo)掃描批量目標(biāo)使用的并發(fā)隊(duì)列功能,去執(zhí)行的執(zhí)行單個(gè)任務(wù),在掃描前做了一些額外的工作把瀏覽器屏蔽的端口過(guò)濾掉了,收到的狀態(tài)就是。 0×00 前言 前兩天 freebuf上的的XSS到內(nèi)網(wǎng)的公開(kāi)課很受啟發(fā),從一個(gè)頁(yè)面到局域網(wǎng),威力著實(shí)增強(qiáng)不少 公開(kāi)課上檢測(cè)內(nèi)網(wǎng) IP 實(shí)現(xiàn)方式用的是 img 標(biāo)簽,加載網(wǎng)站的 fav...
摘要:但是我們明顯能感覺(jué)到這會(huì)降低吞吐量,因?yàn)橄⒉荒懿⑿型哆f了,而且會(huì)阻塞等待,也沒(méi)法發(fā)揮的威力。 最近在看kafka的代碼,就免不了想看看消息隊(duì)列的一些要點(diǎn):服務(wù)質(zhì)量(QOS)、性能、擴(kuò)展性等等,下面一一探索這些概念,并談?wù)勗谔囟ǖ南㈥?duì)列如kafka或者mosquito中是如何具體實(shí)現(xiàn)這些概念的。 服務(wù)質(zhì)量 服務(wù)語(yǔ)義 服務(wù)質(zhì)量一般可以分為三個(gè)級(jí)別,下面說(shuō)明它們不同語(yǔ)義。 At most...
閱讀 964·2023-04-26 02:56
閱讀 9438·2021-11-23 09:51
閱讀 1850·2021-09-26 10:14
閱讀 2980·2019-08-29 13:09
閱讀 2154·2019-08-26 13:29
閱讀 571·2019-08-26 12:02
閱讀 3562·2019-08-26 10:42
閱讀 3000·2019-08-23 18:18