Redian新闻
>
以为很熟悉 CountDownLatch,万万没想到在生产环境翻车了.....

以为很熟悉 CountDownLatch,万万没想到在生产环境翻车了.....

公众号新闻

点击上方“芋道源码”,选择“设为星标

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 
来源:juejin.cn/post/
7129116234804625421

前言

之前分享了CountDownLatch的使用,我们知道用来控制并发流程的同步工具,主要的作用是为了等待多个线程同时完成任务后,在进行主线程任务。

万万没想到,在生产环境中竟然翻车了,因为没有考虑到一些场景,导致了CountDownLatch出现了问题,接下来来分享一下由于CountDownLatch导致的问题。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

需求背景

先简单介绍下业务场景,针对用户批量下载的文件进行修改上传

为了提高执行的速度,所以在采用线程池去执行 下载-修改-上传 的操作,并在全部执行完之后统一提交保存文件地址到数据库,于是加入了CountDownLatch来进行控制。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

具体实现

根据服务本身情况,自定义一个线程池

public static ExecutorService testExtcutor() {
        return new ThreadPoolExecutor(
                2,
                2,
                0L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1));
    }

模拟执行

public static void main(String[] args) {
    // 下载文件总数
    List<Integer> resultList = new ArrayList<>(100);
    IntStream.range(0,100).forEach(resultList::add);
    // 下载文件分段
    List<List<Integer>> split = CollUtil.split(resultList, 10);

    ExecutorService executorService = BaseThreadPoolExector.testExtcutor();
    CountDownLatch countDownLatch = new CountDownLatch(100);
    for (List<Integer> list : split) {
        executorService.execute(() -> {
            list.forEach(i ->{
                try {
                    // 模拟业务操作
                    Thread.sleep(500);
                    System.out.println("任务进入");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(e.getMessage());
                } finally {
                    System.out.println(countDownLatch.getCount());
                    countDownLatch.countDown();
                }
            });
        });
    }
    try {
        countDownLatch.await();
        System.out.println("countDownLatch.await()");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

一开始我个人感觉没有什么问题,反正finally都能够做减一的操作,到最后调用await方法,进行主线程任务

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@300ffa5d rejected from java.util.concurrent.ThreadPoolExecutor@1f17ae12[Running, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 0]
  at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
  at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
  at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
  at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
  at Thread.executor.executorTestBlock.main(executorTestBlock.java:28)
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown
任务进入
countDownLatch.countDown

由于任务数量较多,阻塞队列中已经塞满了,所以默认的拒绝策略,当队列满时,处理策略报错异常,

要注意这个异常是线程池,自己抛出的,不是我们循环里面打印出来的,

这也造成了,线上这个线程池被中断了,打断主线程,执行不到await

利用jstack,我们就能够看到有问题

"pool-1-thread-2" #12 prio=5 os_prio=31 tid=0x00007ff6198b7000 nid=0xa903 waiting on condition [0x0000700001c64000]
   java.lang.Thread.State: WAITING (parking)
 at sun.misc.Unsafe.park(Native Method)
 - parking to wait for  <0x000000076b2283f8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
 at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
 at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
 at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
 at java.lang.Thread.run(Thread.java:748)

"pool-1-thread-1" #11 prio=5 os_prio=31 tid=0x00007ff6198b6800 nid=0x5903 waiting on condition [0x0000700001b61000]
   java.lang.Thread.State: WAITING (parking)
 at sun.misc.Unsafe.park(Native Method)
 - parking to wait for  <0x000000076b2283f8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
 at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
 at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
 at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
 at java.lang.Thread.run(Thread.java:748)

解决方案

  1. 调大阻塞队列,但是问题来了,到底多少阻塞队列才是大呢,如果太大了会不由又造成内存溢出等其他的问题
  2. 在第一个的基础上,我们修改了拒绝策略,当触发拒绝策略的时候,用调用者所在的线程来执行任务
 public static ThreadPoolExecutor queueExecutor(BlockingQueue<Runnable> workQueue){
        return new ThreadPoolExecutor(
                size,
                size,
                0L,
                TimeUnit.SECONDS,
                workQueue,
                new ThreadPoolExecutor.CallerRunsPolicy());
    }
  1. 你可能又会想说,会不会任务数量太多,导致调用者所在的线程执行不过来,任务提交的性能急剧下降

    那我们就应该自定义拒绝策略,将这下排队的消息记录下来,采用补偿机制的方式去执行

  2. 同时也要注意上面的那个异常是线程池抛出来的,我们自己也需要将线程池进行try catch,记录问题数据,并且在finally中执行countDownLatch.countDown来避免,线程池的使用

总结

目前根据业务部门的反馈,业务实际中任务数不很特别多的情况,所以暂时先采用了第二种方式去解决这个线上问题

在这里我们也可以看到,如果没有正确的关闭countDownLatch,可能会导致一直等待,这也是我们需要注意的。

工具虽然好,但是依然要注意他带来的问题,没有正确的去处理好,引发的一系列连锁反应。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
资产缩水上亿美元?万万没想到,全美“亏得最惨”的大学居然是它....9.1入住|Downtown近Suffok/Emerson和橙线地铁站高级公寓一室一厅3000+,接本科生【Downtown】部分户型时间免中介费 近Emerson,市中心高级公寓吴秀波事件,周一围被拖下水,妻子朱丹回应力挺丈夫【Downtown】高级公寓平替 | 全翻新落地大窗|Studio$2800|2B$4000 | 电梯门禁系统【5-9.1入住】【租房】9.1入住|Downtown近Suffok/Emerson和橙线地铁站|高级公寓一室一厅3000+|接本科生五七大院(十)女人为什么要出轨一个小二十岁的男人?四十五岁的女人这样说万万没想到,这次收购的目的竟然是...Mountain View 绝佳位置End Unit Townhouse 带独立后院 Los Altos High 好学区新上市【转租】Downtown高级公寓 | 4/27 - 7/12 | 2b2b | 1700!市中心超高性价比公寓 | 多个入住日期 | Downtown地区 | studio - 3b | 月租$2500+How a Chinese Town Made a Fortune From the World’s Lockdown Pets【现房+排位】【市区顶级公寓】【Emerson/Suffolk/Chinatown/Downtown】【室内洗烘】【接受本科生】波士顿Downtown房源 | 1B1B | 临近河边的翻新公寓 | 9月1号起入住西雅图看房日记|200万以内的98004 Downtown Bellevue自住学区房素食好主意 【简易绿豆糊】?(Simple?Dal)CompletableFuture真香,可以替代CountDownLatch!【9/1】【贵的永远嫌贵,好的永远在排队】【解决方案-->$2450起】【Chinatown/Downtown】【绿/橙/红线】巧克力酸奶油蛋糕(Chocolate Sour Cream Cake)JUC多线程:CountDownLatch、CyclicBarrier、Semaphore 同步器原理当无数用户爱上AI之后…. 万万没想到,AI忽然性情大变!! 这还是我爱的人嘛!万万没想到!内地游客重返香港后,这个东西火了!9人凌晨突袭奥克兰Countdown!国家党:每10小时就有一起飞车撞劫!政府太软弱!暑期或者9.1入住|Downtown近Suffok/Emerson和橙线地铁站高级公寓两室一厅4000,接本科生lāo dao?láo dao!【美食探店】Downtown Duluth美食推荐:Local On NorthDowntown近State地铁站高级公寓1B 3673+ 2B 4346+Downtown高级公寓 | 接本科生 | 2300+ | 楼Emerson | 东北大学 | 0.05英里到橙线【租房】暑期或9.1入住|Downtown|高级公寓2B|4000Malden 最高性价比高级公寓之一,近NEU/Emerson/Downtown/橙线,本科生友好,室内洗烘,2B2B3575+半中介费|9.1入住|可步行到downtown|一室一厅3000,包水暖!避雷!华人自爆:洛杉矶Downtown尽量少去吧!刚租的车就被偷......漫投资第23话丨万万没想到,这次收购的目的竟然是……万万没想到,全公司女生体检这项指标都低....
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。