背景

对于多线程的理解不是非常深刻,工作中用到多线程代码的机会也不多,前不久遇到了一个使用场景,通过编码实现后对于多线程的理解和应用有了更加深刻的理解。场景如下:现有给用户发送产品调研的需求,运营的同事拿来了一个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;// 循环行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内容** @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;// 循环行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内容** @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;}@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;}}
}

批量任务体现多线程的威力!相关推荐

  1. Redis多线程原理详解

    本篇文章为你解答以下问题: 0:redis单线程的实现流程是怎样的? 1:redis哪些地方用到了多线程,哪些地方是单线程? 2:redis多线程是怎么实现的? 3:redis多线程是怎么做到无锁的? ...

  2. 线程导入大数据入库_多线程批量插入数据小结

    在测试的过程中,无法避免的需要做一些性能压测,造数据的时长在此时就会备受关注.比如,造数据的时候用多线程还是多进程,用直接插入DB方式还是用先写文件后导入mysql的方式,写文件是写批量sql后面so ...

  3. python 小说爬虫_从零开始写Python爬虫 --- 1.7 爬虫实践: 排行榜小说批量下载

    从零开始写Python爬虫 --- 1.7 爬虫实践: 排行榜小说批量下载Ehco 5 个月前 本来只是准备做一个爬起点小说名字的爬虫,后来想了一下,为啥不顺便把小说的内容也爬下来呢?于是我就写了这个 ...

  4. 知乎问题:北京,2017,多少k的java web程序员应该懂多线程和jvm优化?

    知乎问题:https://www.zhihu.com/question/59725713/answer/168294369 谢邀,看你问的诚恳,我也好好回答一番. 先说一下我心目的互联网程序员分级: ...

  5. python如何在网络爬虫程序中使用多线程(threading.Thread)

    python如何在网络爬虫程序中使用多线程 一.多线程的基础知识 二.在网络爬虫中使用多线程 2.1 从单线程版本入手 2.2 将单线程版本改写为多线程版本 2.3 运行多线程版本程序 2.4 将多线 ...

  6. 从零开始写Python爬虫 --- 1.7 爬虫实践: 排行榜小说批量下载

    从零开始写Python爬虫 --- 1.7 爬虫实践: 排行榜小说批量下载 Ehco 5 个月前 本来只是准备做一个爬起点小说名字的爬虫,后来想了一下,为啥不顺便把小说的内容也爬下来呢?于是我就写了这 ...

  7. Java多线程编程实战:模拟大量数据同步

    背景 最近对于 Java 多线程做了一段时间的学习,笔者一直认为,学习东西就是要应用到实际的业务需求中的.否则要么无法深入理解,要么硬生生地套用技术只是达到炫技的效果. 不过笔者仍旧认为自己对于多线程 ...

  8. Java多线程-生产者与消费者

    Java多线程生产者与消费者,准确说应该是"生产者-消费者-仓储"模型,使用了仓储,使得生产者消费者模型就显得更有说服力. 对于此模型,应该明确一下几点: 1.生产者仅仅在仓储未满 ...

  9. python多线程下载文件

    看到一篇多线程下载的文章,这里把自己的理解写一篇多线程下载的文章. 我们访问http://192.168.10.7/a.jpg时是get请求,response的head包含Content-Length ...

最新文章

  1. nagios新添加服务有时显示,有时不显示问题解决
  2. hbase shell 查看列名_hbase shell 常用命令
  3. webpack指定第三方模块的查找路径
  4. Win11如何切换应用商店网络 Win11切换应用商店网络的方法
  5. 如何通过Python玩转小视频
  6. Kotlin 和 Flutter 对于开发者究竟意味着什么?
  7. C++ STL 程序员必须会之学会删除容器中的元素 带详细注释实例版本
  8. 前端项目:基于Nodejs+vue开发实现酒店管理系统
  9. 【华为OD机试真题 JS】字符串分割
  10. 软件Hspice基础知识学习笔记(1)
  11. 半功率点为啥是-3dB
  12. 如何搭建一个可以搜题的微信公众号?完整教程
  13. EasyBoot教程三:制作GHOST多重启动盘方法
  14. 伯努利分布、泊松分布
  15. 零零信安王宇:通过基于VPT的风险管理 用20%的时间去解决80%的风险
  16. 良心录屏工具Captura
  17. Python super(钻石继承)
  18. win10 设置ctrl+shift 切换 中文输入法 英文输入法
  19. 502粘到手上变硬了怎么办_手指被502粘住了?别硬扯,教你2招,胶水自动融化...
  20. win10更改时间崩溃

热门文章

  1. 算法导论——动态规划:0-1背包问题(完全解)
  2. java中各进制之间的转换(十进制转十六进制、十进制转二进制、二进制转十进制、二进制转十六进制)...
  3. Spring junit测试
  4. fedora17用yum下载包而不安装包
  5. SQL Sever索引
  6. Ada 程序设计语言(The Ada Programming Language)[第二集]
  7. 移植uboot第四步:设置NAND启动
  8. jQuery 插件 jSlider 图片轮播
  9. 「AHOI / HNOI2018」转盘 解题报告
  10. 规模-复杂世界的简单法则---熵