1. 功能说明
2. 多线程任务示例
2.1 线程池
2.2 单个任务
2.3 任务入口
2.4 结果分析
2.5 源码地址
3. 写在最后
大家好,今天教大家撸一个 Java 的多线程永动任务,这个示例的原型是公司自研的多线程异步任务项目 ,我把里面涉及到多线程的代码抽离出来,然后进行一定的改造。
里面涉及的知识点非常多,特别适合有一定工作经验 的同学学习,或者可以直接拿到项目中使用。
文章结构非常简单:
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(List datas,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(List sourceDatas){ if(CollectionUtils.isEmpty(sourceDatas)){ return; } //将数据拆成4份 List >splitDatas=Lists.partition(sourceDatas,SPLIT_SIZE); finalCountDownLatchlatch=newCountDownLatch(splitDatas.size()); //并发处理拆分的数据,共用一个线程池 for(finalList
datas: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()); } } //获取永动任务数据 privateList queryData(){ List datas=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文章
2964浏览量
104688 -
编程
+关注
关注
88文章
3609浏览量
93680 -
多线程
+关注
关注
0文章
278浏览量
19940 -
代码
+关注
关注
30文章
4774浏览量
68505 -
Thread
+关注
关注
2文章
83浏览量
25921
原文标题:新来个阿里 P7,仅花 2 小时,撸出一个多线程永动任务,看完直接跪了,真牛逼!
文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论