今天教大家做一个 Java 的多线程永动任务,这个示例的原型是公司自研的多线程异步任务项目,我把里面涉及到多线程的代码抽离出来,然后进行一定的改造。

里面涉及的知识点非常多,特别适合有一定工作经验的同学学习,或者可以直接拿到项目中使用。

文章结构非常简单:

1. 功能说明

做这个多线程异步任务,主要是因为我们有很多永动的异步任务,什么是永动呢?就是任务跑起来后,需要一直跑下去。

比如消息 Push 任务,因为一直有消息过来,所以需要一直去消费 DB 中的未推送消息,就需要整一个 Push 的永动异步任务。

我们的需求其实不难,简单总结一下:

  • 能同时执行多个永动的异步任务;
  • 每个异步任务,支持开多个线程去消费这个任务的数据;
  • 支持永动异步任务的优雅关闭,即关闭后,需要把所有的数据消费完毕后,再关闭。

完成上面的需求,需要注意几个点:

  • 每个永动任务,可以开一个线程去执行;
  • 每个子任务,因为需要支持并发,需要用线程池控制;
  • 永动任务的关闭,需要通知子任务的并发线程,并支持永动任务和并发子任务的优雅关闭。

2. 多线程任务示例

2.1 线程池

对于子任务,需要支持并发,如果每个并发都开一个线程,用完就关闭,对资源消耗太大,所以引入线程池:

public class TaskProcessUtil {private static Map<String, ExecutorService> executors = new ConcurrentHashMap<>();private static ExecutorService init(String poolName, int poolSize) {return new ThreadPoolExecutor(poolSize, poolSize,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),new ThreadFactoryBuilder().setNameFormat("Pool-" + poolName).setDaemon(false).build(),new ThreadPoolExecutor.CallerRunsPolicy());}public static ExecutorService getOrInitExecutors(String poolName,int poolSize) {ExecutorService executorService = executors.get(poolName);if (null == executorService) {synchronized (TaskProcessUtil.class) {executorService = executors.get(poolName);if (null == executorService) {executorService = init(poolName, poolSize);executors.put(poolName, executorService);}}}return executorService;}public static void releaseExecutors(String poolName) {ExecutorService executorService = executors.remove(poolName);if (executorService != null) {executorService.shutdown();}}
}

这是一个线程池的工具类,这里初始化线程池和回收线程资源很简单,我们主要讨论获取线程池。

获取线程池可能会存在并发情况,所以需要加一个 synchronized 锁,然后锁住后,需要对 executorService 进行二次判空校验。

2.2 单个任务

为了更好讲解单个任务的实现方式,我们的任务主要就是把 Cat 的数据打印出来,Cat 定义如下:

@Data
@Service
public class Cat {private String catName;public Cat setCatName(String name) {this.catName = name;return this;}
}

单个任务主要包括以下功能:

获取永动任务数据:这里一般都是扫描 DB,我直接就简单用 queryData() 代替。

多线程执行任务:需要把数据拆分成 4 份,然后分别由多线程并发执行,这里可以通过线程池支持;

永动任务优雅停机:当外面通知任务需要停机,需要执行完剩余任务数据,并回收线程资源,退出任务;

永动执行:如果未收到停机命令,任务需要一直执行下去。

直接看代码:

public class ChildTask {private final int POOL_SIZE = 3; private final int SPLIT_SIZE = 4; private String taskName;protected volatile boolean terminal = false;public ChildTask(String taskName) {this.taskName = taskName;}public void doExecute() {int i = 0;while(true) {System.out.println(taskName + ":Cycle-" + i + "-Begin");List<Cat> datas = queryData();taskExecute(datas);System.out.println(taskName + ":Cycle-" + i + "-End");if (terminal) {break;}i++;}TaskProcessUtil.releaseExecutors(taskName);}public void terminal() {terminal = true;System.out.println(taskName + " shut down");}private void doProcessData(List<Cat> datas, CountDownLatch latch) {try {for (Cat cat : datas) {System.out.println(taskName + ":" + cat.toString() + ",ThreadName:" + Thread.currentThread().getName());Thread.sleep(1000L);}} catch (Exception e) {System.out.println(e.getStackTrace());} finally {if (latch != null) {latch.countDown();}}}private void taskExecute(List<Cat> sourceDatas) {if (CollectionUtils.isEmpty(sourceDatas)) {return;}List<List<Cat>> splitDatas = Lists.partition(sourceDatas, SPLIT_SIZE);final CountDownLatch latch = new CountDownLatch(splitDatas.size());for (final List<Cat> datas : splitDatas) {ExecutorService executorService = TaskProcessUtil.getOrInitExecutors(taskName, POOL_SIZE);executorService.submit(new Runnable() {@Overridepublic void run() {doProcessData(datas, latch);}});}try {latch.await();} catch (Exception e) {System.out.println(e.getStackTrace());}}private List<Cat> queryData() {List<Cat> datas = new ArrayList<>();for (int i = 0; i < 5; i ++) {datas.add(new Cat().setCatName("罗小黑" + i));}return datas;}
}

简单解释一下:

  • queryData:用于获取数据,实际应用中其实是需要把 queryData 定为抽象方法,然后由各个任务实现自己的方法。
  • doProcessData:数据处理逻辑,实际应用中其实是需要把 doProcessData 定为抽象方法,然后由各个任务实现自己的方法。
  • taskExecute:将数据拆分成 4 份,获取该任务的线程池,并交给线程池并发执行,然后通过 latch.await() 阻塞。当这 4 份数据都执行成功后,阻塞结束,该方法才返回。
  • terminal:仅用于接受停机命令,这里该变量定义为 volatile,所以多线程内存可见;

doExecute:程序执行入口,封装了每个任务执行的流程,当 terminal=true 时,先执行完任务数据,然后回收线程池,最后退出。

2.3 任务入口

直接上代码:

public class LoopTask {private List<ChildTask> childTasks;public void initLoopTask() {childTasks = new ArrayList();childTasks.add(new ChildTask("childTask1"));childTasks.add(new ChildTask("childTask2"));for (final ChildTask childTask : childTasks) {new Thread(new Runnable() {@Overridepublic void run() {childTask.doExecute();}}).start();}}public void shutdownLoopTask() {if (!CollectionUtils.isEmpty(childTasks)) {for (ChildTask childTask : childTasks) {childTask.terminal();}}}public static void main(String args[]) throws Exception{LoopTask loopTask = new LoopTask();loopTask.initLoopTask();Thread.sleep(5000L);loopTask.shutdownLoopTask();}
}

每个任务都开一个单独的 Thread,这里我初始化了 2 个永动任务,分别为 childTask1 和 childTask2,然后分别执行,后面 Sleep 了 5 秒后,再关闭任务,我们可以看看是否可以按照我们的预期优雅退出。

2.4 结果分析

执行结果如下:

childTask1:Cycle-0-Begin
childTask2:Cycle-0-Begin
childTask1:Cat(catName=罗小黑0),ThreadName:Pool-childTask1
childTask1:Cat(catName=罗小黑4),ThreadName:Pool-childTask1
childTask2:Cat(catName=罗小黑4),ThreadName:Pool-childTask2
childTask2:Cat(catName=罗小黑0),ThreadName:Pool-childTask2
childTask1:Cat(catName=罗小黑1),ThreadName:Pool-childTask1
childTask2:Cat(catName=罗小黑1),ThreadName:Pool-childTask2
childTask2:Cat(catName=罗小黑2),ThreadName:Pool-childTask2
childTask1:Cat(catName=罗小黑2),ThreadName:Pool-childTask1
childTask2:Cat(catName=罗小黑3),ThreadName:Pool-childTask2
childTask1:Cat(catName=罗小黑3),ThreadName:Pool-childTask1
childTask2:Cycle-0-End
childTask2:Cycle-1-Begin
childTask1:Cycle-0-End
childTask1:Cycle-1-Begin
childTask2:Cat(catName=罗小黑0),ThreadName:Pool-childTask2
childTask2:Cat(catName=罗小黑4),ThreadName:Pool-childTask2
childTask1:Cat(catName=罗小黑4),ThreadName:Pool-childTask1
childTask1:Cat(catName=罗小黑0),ThreadName:Pool-childTask1
childTask1 shut down
childTask2 shut down
childTask2:Cat(catName=罗小黑1),ThreadName:Pool-childTask2
childTask1:Cat(catName=罗小黑1),ThreadName:Pool-childTask1
childTask1:Cat(catName=罗小黑2),ThreadName:Pool-childTask1
childTask2:Cat(catName=罗小黑2),ThreadName:Pool-childTask2
childTask1:Cat(catName=罗小黑3),ThreadName:Pool-childTask1
childTask2:Cat(catName=罗小黑3),ThreadName:Pool-childTask2
childTask1:Cycle-1-End
childTask2:Cycle-1-End

输出数据:

  • “Pool-childTask” 是线程池名称;
  • “childTask” 是任务名称;
  • “Cat(catName=罗小黑)” 是执行的结果;
  • “childTask shut down” 是关闭标记;
  • “childTask:Cycle-X-Begin” 和“childTask:Cycle-X-End” 是每一轮循环的开始和结束标记。

我们分析一下执行结果:

  • childTask1 和 childTask2 分别执行,在第一轮循环中都正常输出了 5 条罗小黑数据;
  • 第二轮执行过程中,我启动了关闭指令,这次第二轮执行没有直接停止,而是先执行完任务中的数据,再执行退出,所以完全符合我们的优雅退出结论。

2.5 源码地址

GitHub 地址:

https://github.com/lml200701158/java-study/tree/master/src/main/java/com/java/parallel/pool/ofc

3. 写在最后

对于这个经典的线程池使用示例,原项目是我好友一灰写的,技术水平对标阿里 P7,实现得也非常优雅,涉及的知识点非常多,非常值得大家学习。

新来个阿里 P7,仅花 2 小时,做出一个多线程永动任务,看完直接跪了相关推荐

  1. 新来个阿里 P7,仅花 2 小时,撸出一个多线程永动任务,看完直接跪了,真牛逼

    大家好,我是楼仔! 今天教大家撸一个 Java 的多线程永动任务,这个示例的原型是公司自研的多线程异步任务项目,我把里面涉及到多线程的代码抽离出来,然后进行一定的改造. 里面涉及的知识点非常多,特别适 ...

  2. 新来个阿里 P7,仅花 2 小时,撸出一个多线程永动任务,看完直接跪了,真牛逼!...

    大家好,今天教大家撸一个 Java 的多线程永动任务,这个示例的原型是公司自研的多线程异步任务项目,我把里面涉及到多线程的代码抽离出来,然后进行一定的改造. 里面涉及的知识点非常多,特别适合有一定工作 ...

  3. 新来个技术总监,仅花2小时,撸出一个多线程永动任务,看完直接跪了,真牛逼!

    今天教大家撸一个 Java 的多线程永动任务,这个示例的原型是公司自研的多线程异步任务项目,我把里面涉及到多线程的代码抽离出来,然后进行一定的改造. 里面涉及的知识点非常多,特别适合有一定工作经验的同 ...

  4. 公司新来的阿里P7大牛,只用十分钟就教会了我实现高层次的复用

    最新互联网大厂面试真题.Java程序员面试策略(面试前的准备.面试中的技巧)请移步GitHub 作为开发人员,你对复用这个概念一定不陌生.在开发过程中,我们把系统中通用的代码逻辑抽取出来,变成公共方法 ...

  5. 解析底层原理!阿里P7级别面试经验总结,2年以上经验必看

    上周喜提oppo面试offer,本人在深圳,有4年多的Android项目经验,普通本科学历. 面试是相互相在,OPPO给我的印象确实如其核心价值观所说:本分. 一面.部长面和HR面都蛮顺利(一千个人可 ...

  6. 吓人!仅花20小时18美元,可瞬间破译1100万个密码

    全文共2512字,预计学习时长4分钟 事情是这样发生的. 在某个阳光明媚的下午,热心市民刘先生正吃着火锅唱着歌,"叮~",手机突然收到一条短信.以为是系统短信的他,起期初毫不在意, ...

  7. 订单突破10000+,仅花1小时,APPx独家深入剖析背后的秘密!

    拼多多:成立三年,获客三亿,月订单成交额达到恐怖的400亿,成功上市! 糕妈优选:营销活动推**送1小时,**订单超过10000+,商品成功刷屏朋友圈! 寻慢:一场活动净增7000+粉丝,付款转化率高 ...

  8. 阿里正式启动2021届春季校招!java算法工程师,看完跪了

    容器化时代来了 虚拟化技术已经走过了三个时代,没有容器化技术的演进就不会有 Docker 技术的诞生. (1)物理机时代:多个应用程序可能会跑在一台机器上. (2)虚拟机时代:一台物理机器安装多个虚拟 ...

  9. 阿里P8架构大神分享纯手写“kafka文档”看完直呼太牛!

    什么是 Kafka Kafka 是由 Linkedin 公司开发的,它是一个分布式的,支持多分区.多副本,基于 Zookeeper 的分布式消息流平台,它同时也是一款开源的基于发布订阅模式的消息引擎系 ...

最新文章

  1. es6箭头函数(=)与展开特性运算符(...)的使用
  2. Istio调用链埋点原理剖析—是否真的“零修改”?
  3. apache php 调优_Apache的性能优化(二)
  4. 数据库面试题【十八、优化关联查询优化子查询优化LIMIT分页优化UNION查询优化WHERE子句】
  5. 设计高效sql一般经验谈
  6. 虚拟机中访问连接在物理机上的摄像机(使用桥接)
  7. 库卡机器人CELL程序解析
  8. 消息推送生命周期_一套完整的APP推送体系方案|附思维导图
  9. java set的作用,Java的自学之路-构造方法 的作用以及与set方法的区别
  10. 无所不在的K.I.S.S——《Unix编程艺术》书评
  11. 【解决方案 六】---在VS2015里使用ILDasm
  12. SPSS如何验证是否符合正态分布
  13. UML用例图讲解及画法
  14. 飞秋FeiQ2013特色功能列表
  15. 网站设计流程分为哪几个步骤
  16. shell用户输入数字加法操作
  17. 虚拟存储器与虚拟内存
  18. C语言、编程语言发展史
  19. sudo su后获取不到JAVA_HOME环境变量的解决方案
  20. android发送数据短信,如何发送和接收数据短信

热门文章

  1. Nginx SPDY patch实现
  2. 来看看这位年轻的 eBay 小伙是如何成为 Committer
  3. 如何画好场景速写:独特+夸张轻松应对
  4. 机器学习中常见的评估方法
  5. 游戏建模你必须要掌握的六类软件
  6. 通信原理简明教程 | 信号、信道与噪声
  7. Problem B Problem I
  8. 365天挑战LeetCode1000题——Day 103 400题 检查二进制字符串字段 最大子序列交替和 最低票价 K 站中转内最便宜的航班
  9. 计算机专业的swot分析图,计算机专业学生个人SWOT分析示例.doc
  10. 【调剂】内蒙古农业大学2020年硕士研究生调剂公告(一)