0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Java多线程永动任务 多线程异步任务项目解读

jf_ro2CN3Fa 来源:楼仔 作者:楼仔 2022-10-19 11:46 次阅读

1. 功能说明

2. 多线程任务示例

2.1 线程池

2.2 单个任务

2.3 任务入口

2.4 结果分析

2.5 源码地址

3. 写在最后

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

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

文章结构非常简单:

ae8ef34e-4cfe-11ed-a3b6-dac502259ad0.png

1. 功能说明

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

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

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

能同时执行多个永动的异步任务

每个异步任务,支持开多个线程 去消费这个任务的数据;

支持永动异步任务的优雅关闭 ,即关闭后,需要把所有的数据消费完毕后,再关闭。

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

每个永动任务 ,可以开一个线程去执行;

每个子任务 ,因为需要支持并发,需要用线程池控制;

永动任务的关闭,需要通知子任务的并发线程,并支持永动任务和并发子任务的优雅关闭

2. 多线程任务示例

2.1 线程池

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

publicclassTaskProcessUtil{
//每个任务,都有自己单独的线程池
privatestaticMapexecutors=newConcurrentHashMap<>();

//初始化一个线程池
privatestaticExecutorServiceinit(StringpoolName,intpoolSize){
returnnewThreadPoolExecutor(poolSize,poolSize,
0L,TimeUnit.MILLISECONDS,
newLinkedBlockingQueue(),
newThreadFactoryBuilder().setNameFormat("Pool-"+poolName).setDaemon(false).build(),
newThreadPoolExecutor.CallerRunsPolicy());
}

//获取线程池
publicstaticExecutorServicegetOrInitExecutors(StringpoolName,intpoolSize){
ExecutorServiceexecutorService=executors.get(poolName);
if(null==executorService){
synchronized(TaskProcessUtil.class){
executorService=executors.get(poolName);
if(null==executorService){
executorService=init(poolName,poolSize);
executors.put(poolName,executorService);
}
}
}
returnexecutorService;
}

//回收线程资源
publicstaticvoidreleaseExecutors(StringpoolName){
ExecutorServiceexecutorService=executors.remove(poolName);
if(executorService!=null){
executorService.shutdown();
}
}
}

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

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

2.2 单个任务

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

@Data
@Service
publicclassCat{
privateStringcatName;
publicCatsetCatName(Stringname){
this.catName=name;
returnthis;
}
}

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

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

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

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

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

直接看代码:

publicclassChildTask{

privatefinalintPOOL_SIZE=3;//线程池大小
privatefinalintSPLIT_SIZE=4;//数据拆分大小
privateStringtaskName;

//接收jvm关闭信号,实现优雅停机
protectedvolatilebooleanterminal=false;

publicChildTask(StringtaskName){
this.taskName=taskName;
}

//程序执行入口
publicvoiddoExecute(){
inti=0;
while(true){
System.out.println(taskName+":Cycle-"+i+"-Begin");
//获取数据
Listdatas=queryData();
//处理数据
taskExecute(datas);
System.out.println(taskName+":Cycle-"+i+"-End");
if(terminal){
//只有应用关闭,才会走到这里,用于实现优雅的下线
break;
}
i++;
}
//回收线程池资源
TaskProcessUtil.releaseExecutors(taskName);
}

//优雅停机
publicvoidterminal(){
//关机
terminal=true;
System.out.println(taskName+"shutdown");
}

//处理数据
privatevoiddoProcessData(Listdatas,CountDownLatchlatch){
try{
for(Catcat:datas){
System.out.println(taskName+":"+cat.toString()+",ThreadName:"+Thread.currentThread().getName());
Thread.sleep(1000L);
}
}catch(Exceptione){
System.out.println(e.getStackTrace());
}finally{
if(latch!=null){
latch.countDown();
}
}
}

//处理单个任务数据
privatevoidtaskExecute(ListsourceDatas){
if(CollectionUtils.isEmpty(sourceDatas)){
return;
}
//将数据拆成4份
List>splitDatas=Lists.partition(sourceDatas,SPLIT_SIZE);
finalCountDownLatchlatch=newCountDownLatch(splitDatas.size());

//并发处理拆分的数据,共用一个线程池
for(finalListdatas:splitDatas){
ExecutorServiceexecutorService=TaskProcessUtil.getOrInitExecutors(taskName,POOL_SIZE);
executorService.submit(newRunnable(){
@Override
publicvoidrun(){
doProcessData(datas,latch);
}
});
}

try{
latch.await();
}catch(Exceptione){
System.out.println(e.getStackTrace());
}
}

//获取永动任务数据
privateListqueryData(){
Listdatas=newArrayList<>();
for(inti=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 任务入口

直接上代码:

publicclassLoopTask{
privateListchildTasks;
publicvoidinitLoopTask(){
childTasks=newArrayList();
childTasks.add(newChildTask("childTask1"));
childTasks.add(newChildTask("childTask2"));
for(finalChildTaskchildTask:childTasks){
newThread(newRunnable(){
@Override
publicvoidrun(){
childTask.doExecute();
}
}).start();
}
}
publicvoidshutdownLoopTask(){
if(!CollectionUtils.isEmpty(childTasks)){
for(ChildTaskchildTask:childTasks){
childTask.terminal();
}
}
}
publicstaticvoidmain(Stringargs[])throwsException{
LoopTaskloopTask=newLoopTask();
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
childTask1shutdown
childTask2shutdown
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级别,实现得也非常优雅,涉及的知识点非常多 ,非常值得大家学习。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2904

    浏览量

    102994
  • 编程
    +关注

    关注

    88

    文章

    3440

    浏览量

    92389
  • 多线程
    +关注

    关注

    0

    文章

    271

    浏览量

    19724
  • 代码
    +关注

    关注

    30

    文章

    4555

    浏览量

    66767
  • Thread
    +关注

    关注

    2

    文章

    83

    浏览量

    25699

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

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Java多线程的用法

    本文将介绍一下Java多线程的用法。 基础介绍 什么是多线程 指的是在一个进程中同时运行多个线程,每个线程都可以独立执行不同的
    的头像 发表于 09-30 17:07 621次阅读

    鸿蒙原生应用开发-ArkTS语言基础类库多线程I/O密集型任务开发

    使用异步并发可以解决单次I/O任务阻塞的问题,但是如果遇到I/O密集型任务,同样会阻塞线程中其它任务的执行,这时需要使用
    发表于 03-21 14:57

    Java基础学习多线程使用指南

    黑马程序员-----Java基础学习多线程
    发表于 10-08 14:10

    TWEN-ASR ONE 语音识别系列教程(4)---多线程与消息队列使用

    ,调用任务B,任务B开始执行。时间Tick到,调用任务A,任务A接着刚才执行,时间Tick到,调用任务B,
    发表于 07-02 16:27

    介绍Arduino借助多线程SCoop库如何轻松实现并发处理任务

    我们在用Arduino开发复杂项目时,或多或少会面临多任务同时处理的工作场景,本篇简单介绍Arduino借助多线程SCoop库如何轻松实现并发处理任务
    发表于 08-24 06:57

    什么时候要使用多线程

    什么时候要使用多线程:cpu密集型:(比如一个while( true ){ i++;})IO密集型:(比如一个从磁盘拷贝数据到另一个磁盘的拷贝进程)1)计算密集型任务。此时要尽量使用多线程,可以提高
    发表于 09-06 07:25

    请问CubeMX如何配置FreeRTOS跑多线程任务

    请问CubeMX如何配置FreeRTOS跑多线程任务
    发表于 02-14 06:39

    如何使用多线程异步操作等并发设计方法来最大化程序的性能

    任务。  不过有个问题,异步有时优先级比主线程还高。这个特点和多线程不同。  原作者:jingjin221
    发表于 08-23 16:31

    基于单片机系统中的多任务多线程机制的实现

       首先要指出的是一点是,我们不是讨论嵌入式实时多任务操作系统(RTOS)的设计。我们讨论的是,在不使用RTOS的控制系统中,如何体现多任务多线程机制的程序设计思
    发表于 09-26 16:12 1832次阅读

    java多线程设计模式_结城浩

    JAVA多线程设计模式》通过浅显易懂的文字与实例来介绍JAVA线程相关的设计模式概念,并且通过实际的JAVA程序范例和UML图示来一一解说
    发表于 01-05 16:15 0次下载
    <b class='flag-5'>java</b><b class='flag-5'>多线程</b>设计模式_结城浩

    多线程好还是单线程好?单线程多线程的区别 优缺点分析

    摘要:如今单线程多线程已经得到普遍运用,那么到底多线程好还是单线程好呢?单线程多线程的区别又
    发表于 12-08 09:33 8w次阅读

    什么是多线程编程?多线程编程基础知识

    摘要:多线程编程是现代软件技术中很重要的一个环节。要弄懂多线程,这就要牵涉到多进程。本文主要以多线程编程以及多线程编程相关知识而做出的一些结论。
    发表于 12-08 16:30 1.2w次阅读

    java学习——java面试【事务、锁、多线程】资料整理

    本文档内容介绍了基于java学习java面试【事务、锁、多线程】资料整理,供参考
    发表于 03-13 13:53 0次下载

    多线程如何保证数据的同步

    多线程编程是一种并发编程的方法,意味着程序中同时运行多个线程,每个线程可独立执行不同的任务,共享同一份数据。由于多线程并发执行的特点,会引发
    的头像 发表于 11-17 14:22 350次阅读

    java实现多线程的几种方式

    Java实现多线程的几种方式 多线程是指程序中包含了两个或以上的线程,每个线程都可以并行执行不同的任务
    的头像 发表于 03-14 16:55 174次阅读