【自省】使用Executors.xxx违反阿里Java代码规范,那还不写定时任务了?

news2024/11/17 23:39:47

一、背景

在《分布式锁主动续期的入门级实现-自省 | 简约而不简单》中通过【自省】的方式讨论了关于分布式锁自动续期功能的入门级实现方式,简单同步一下上下文:

  1. 客户端抢到分布式锁之后开始执行任务,执行完毕后再释放分布式锁。
  2. 持锁后因客户端异常未能把锁释放,会导致锁成为永恒锁。
  3. 为了避免这种情况,在创建锁的时候给锁指定一个过期时间。
  4. 到期之后锁会被自动删除掉,这个角度看是对锁资源的一种保护。
  5. 重点:但若锁过期被删除后,任务还没结束怎么办?
  6. 可以通过在一个额外的线程中主动推迟分布式锁的过期时间,下文也用续期一词来表述;避免当任务还没执行完,锁就被删除了

二、理还乱?

逻辑看很简单,也很清晰,但任何事情都有两面性,每个锁配一个额外的线程做watchDog专门去处理,实现起来自然简单清晰,但肯定也有弊端。如果要把锁的功能做的健壮,总要从不断地自我质疑、自我反思中,理顺思路,寻找答案,我认为这属于自省式学习,以后也想尝试这种模式,一起来试试吧:

  • 问题:如果同时有成百上千个锁呢?

    同时有成百上千个锁,按照上篇中的实现方式,就会对应创建成百上千个线程在做续期工作,但实际上间歇性的续租操作并非高并发操作,只需要几个线程即可。类比一下一群羊只要少数牧羊犬看护的情景?

  • 问题:什么场景下会有同时出现这么多锁呢?

    如运营要做抢购活动,那么就会瞬间有成百上千的下单请求进入服务中,在高并发场景下特别容易出现超时而导致 rpc 重试 ,而这时需要拥有一种防重入的自保护机制的。对防重入感兴趣的这里提供一个直通车:《分布式锁中-基于 Redis 的实现如何防重入》。

  • 问题:如何避免创建这么多线程呢?

    池化机制,Java 中提供了用于执行调度任务的线程池,如 ScheduledExecutorService#scheduleAtFixedRate

  • 问题:如何构建

    示例:

     new ScheduledThreadPoolExecutor(corePoolSize,
        new NamedThreadFactory("defaultKeepAlivePool-", true),
        new ThreadPoolExecutor.AbortPolicy()
    复制代码

    构造函数详情:

    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory,
                                       RejectedExecutionHandler handler) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory, handler);
    }
    复制代码
  • 问题:线程池的构建居然敢用 Integer.MAX_VALUE

    这构造方法明显有问题,你该知道Integer.MAX_VALUE违反了阿里 Java 代码规范。

  • 问题:哪一条规范?

    看下边这条规范中,明确指出允许的请求队列长度为Integer.MAX_VALUE,可能会 xxx 导致 OOM

  • 问题:有Integer.MAX_VALUE就能 OOM 了?

    是的,交给线程池执行的是一个个任务对象,每个任务对象都会占用一定的内存,当线程池处理任务的能力降低,任务数越来越多的时候就 OOM 了。

  • 问题:能举个例子嘛?

    设置 JVM 内存-Xms200m -Xmx200m,JVM 内存上限设定小一些,每个任务里占用的内存给大一些,加速 OOM 报错。

    // 调整VM参数,加速OOM:-Xms200m -Xmx200m
    public static void testOOM(){
        ScheduledExecutorService scheduledExecutorService  =  Executors.newScheduledThreadPool(1);
        try {
            for(int i = 0;i<10000000;i++){
                scheduledExecutorService.scheduleAtFixedRate(() -> {
                    ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor)scheduledExecutorService;
                    int size = executor.getQueue().size();
                    System.out.println("size : " + size);
                    //为了快速复现,任务里给个大内存占用
                    byte[] array = new byte[1024*1024*10];
                    try {
                        TimeUnit.MINUTES.sleep(10);
                        int length  = array.length;
                        System.out.println(length);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                },5,5,TimeUnit.SECONDS);
            }
            TimeUnit.HOURS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    复制代码
  • 问题:结果啥样?

    果然模拟出了 OOM,结果如下:

    size : 1637400
    size : 1637778
    ...
    Exception in thread "main"
    java.lang.OutOfMemoryError: GC overhead limit exceeded
            at java.util.concurrent.Executors.callable(Executors.java:407)
            at java.util.concurrent.FutureTask.<init>(FutureTask.java:152)
            at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.<init>(ScheduledThreadPoolExecutor.java:219)
            at java.util.concurrent.ScheduledThreadPoolExecutor.scheduleAtFixedRate(ScheduledThreadPoolExecutor.java:570)
            at TestSchedule.testOOM(TestSchedule.java:107
            at TestSchedule.main(TestSchedule.java:8)
    复制代码
  • 问题:那ScheduledThreadPool就不能用了?

    看场景,对于分布式锁看门口的场景下,一个系统中除非恶意写 bug;否则按照常规并发设置,如 Dubbo Provider 实例中,一般线程数是设置为 200,那并发情况下同时也就存在 200 个锁,从续期任务的提交看,最多只有 200 个,这个任务数,远远不到 Integer.MAX。

  • 问题:那要是就无限创建锁呢? 这种不讲场景的无限 xxx 的操作是 bug。很多操作被无限 xxx 后都能 OOM 。

  • 问题:那我只能用它了?

    从网络中搜集到的资料情况来看,ScheduledThreadPool最多,读者老师若熟悉其它的池化调度组件,也烦请留言告知。

  • 问题:虽然只提交了 200 个任务,但任务是定时触发的,这有风险的呀?

    定时重复执行的任务,如果每 5 秒执行一次,一个任务执行的耗时在 30 秒,那任务数就越来越多了。

  • 问题:定时任务数会因执行慢而越来越多嘛?

    public static void testInterval(){
    ScheduledExecutorService scheduledExecutorService =  Executors.newScheduledThreadPool(1);
    try {
        System.out.println("start : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                System.out.println("enter : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
                TimeUnit.SECONDS.sleep(10);
                System.out.println("exist : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },5,5,TimeUnit.SECONDS);
    
        TimeUnit.HOURS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    }
    复制代码

    任务没结束时,下个任务并未开始,而是等任务结束后才开始,那就没有了耗时大于定时间隔,而导致任务越来越多的风险了。

    start : 2022-12-06T17:33:39.177 // 任务开始
    enter : 2022-12-06T17:33:44.239 // 5秒后首次执行
    exist : 2022-12-06T17:33:54.239 // 任务耗时10秒
    enter : 2022-12-06T17:33:54.239 // 要求任务间隔是5秒,但已耗时10秒,超过了5秒,任务没结束时,下个任务并未开始,而是等任务结束后才开始
    exist : 2022-12-06T17:34:04.24
    enter : 2022-12-06T17:34:04.24
    exist : 2022-12-06T17:34:14.241
    enter : 2022-12-06T17:34:14.241
    exist : 2022-12-06T17:34:24.241
    复制代码
  • 问题:那任务执行耗时 小于 定时间隔的,什么时机开始下一次任务呢?

        public static void testInterval(){
            ScheduledExecutorService scheduledExecutorService =  Executors.newScheduledThreadPool(1);
            try {
                System.out.println("start : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
                scheduledExecutorService.scheduleAtFixedRate(() -> {
                    try {
                        System.out.println("enter : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
                        TimeUnit.SECONDS.sleep(5);
                        System.out.println("exist : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                },10,10,TimeUnit.SECONDS);
    
                TimeUnit.HOURS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        ```
    
        ```
        start : 2022-12-06T17:31:01.771 //任务开始
        enter : 2022-12-06T17:31:11.846 //10秒后首次执行
        exist : 2022-12-06T17:31:16.846 //任务耗时5秒
        enter : 2022-12-06T17:31:21.845 //因间隔10秒,任务耗时5秒,所以5秒后继续重新执行任务
        exist : 2022-12-06T17:31:26.846
        enter : 2022-12-06T17:31:31.846
        exist : 2022-12-06T17:31:36.846
        enter : 2022-12-06T17:31:41.847
        exist : 2022-12-06T17:31:46.847
    复制代码
  • 问题:所以scheduleAtFixedRate的逻辑结论是?

    • 如果上一个任务的执行时间大于等待时间,任务结束后,下一个任务马上执行。
    • 如果上一个任务的执行时间小于等待时间,任务结束后,下一个任务在(间隔时间-执行时间)后开始执行。
  • 问题:那 scheduleWithFixedDealy 呢?

    • 如果上个任务的执行时间大于等待时间,任务结束后也会等待相应的时间才执行下一个任务

三、新的思考

  • 问题:那阿里的规范写的有问题?

    可再认真阅读此规范,微妙之处请读者老师自行品鉴,也可留言讨论

  • 问题:那是不是就可以放心大胆的霍霍了?

    那肯定是不能随心所欲的。

  • 问题:那还有什么注意事项?

    还不累嘛?休息休息,日更该发稿了,要不审核就下班了。

  • 问题:掘金审核还下班?

    好问题,审核是什么机制呢,欢迎留言讨论,咱们下一篇再聊。


四、最后说一句

我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/77975.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务守护神-Sentinel-热点-授权-系统规则

引言 书接上篇 微服务守护神-Sentinel-降级规则 &#xff0c;上面介绍了Sentinel降级规则&#xff0c;本篇继续来Sentinel的热点、授权、系统规则。 热点规则 何为热点&#xff1f;热点即经常访问的数据。很多时候我们希望统计某些热点数据中访问频次最高的 Top K 数据&…

# String-security(配置异常处理器,封装JWT工具类)

1 JWT是做什么的? 为了在前后端分离项目中使用 JWT &#xff0c;我们需要达到 2 个目标&#xff1a; 在用户登录认证成功后&#xff0c;需要返回一个含有 JWT token 的 json 串。 在用户发起的请求中&#xff0c;如果携带了正确合法的 JWT token &#xff0c;后台需要放行&a…

ELK日志分析系统

目录 一.ELK概述 1.1 ELK简介 1.2 ELK日志组件 1.2.1 ELK组件介绍 1.3 日志处理步骤 二. Elasticsearch 集群部署 2.1 关闭防火墙 ​2.2 ELK Elasticsearch 集群部署&#xff08;在Node1&#xff0c;Node2节点上操作&#xff09; ​ 2.2.1 更改主机名​ 2.2.2 配置域名…

RCE漏洞简介

今天继续给大家介绍渗透测试相关知识&#xff0c;本文主要内容是RCE漏洞简介。 免责声明&#xff1a; 本文所介绍的内容仅做学习交流使用&#xff0c;严禁利用文中技术进行非法行为&#xff0c;否则造成一切严重后果自负&#xff01; 再次强调&#xff1a;严禁对未授权设备进行…

高级篇之如何升级5GCPE固件

高级篇之如何升级5GCPE固件1. 准备工作2. 安装5GCPE串口驱动3. 升级固件3.1 选择固件3.2 选择串口号3.3 下载固件3.4 下载固件意外情况4. 重新启动结束&#xff01;LINKPI-5GCPE是LINKPI推出的可以做户外移动直播的 5G无线网关&#xff0c;可以支持三种模式&#xff1a; 网卡模…

计算机网络-分组交换与电路交换

有志者&#xff0c;事竟成 文章持续更新&#xff0c;可以关注【小奇JAVA面试】第一时间阅读&#xff0c;回复【资料】获取福利&#xff0c;回复【项目】获取项目源码&#xff0c;回复【简历模板】获取简历模板&#xff0c;回复【学习路线图】获取学习路线图。 文章目录一、分组…

普乐蛙VR航天科技馆太空体验馆VR太空舱体验馆vr飞碟遨游太空

什么是航天航空主题馆 普乐蛙VR航天航空体验馆系列 普乐蛙VR航天航空主题馆可以根据客户的需求&#xff0c;用航天航空的科技氛围方案进行布置&#xff0c;大多用最新的黑科技&#xff0c;让整个馆充满科技科幻的感觉&#xff0c;使人沉浸&#xff0c;容易进入主题。馆内设置不…

MySQL函数:列转行CONCAT、CONCAT_WS、GROUP_CONCAT的使用(精要)

前言 很久没有接触Mysql了。 今天心血来潮&#xff0c;突然想了解一下Mysql列转行&#xff0c;看了一些文章&#xff0c;重点不清晰&#xff0c;遂有下文&#xff01; Mysql官网、 社区版下载&#xff08; Windows版_mysql.8.0.31下载 &#xff09; 概述 Mysql内部提供了列转…

UNIAPP实战项目笔记50 登录和注册页面的布局

UNIAPP实战项目笔记50 登录和注册页面的布局 实际案例图片 登录页面布局 注册页面布局 显示登录和注册页面布局 页面布局的切换 具体内容图片自己替换哈&#xff0c;随便找了个图片的做示例 具体位置见目录结构 完善布局页面和样式 代码 login.vue部分 <template><v…

拉线援(AD PADS Cadence快捷键汇总)

年纪越大&#xff0c;记性就不太好了&#xff0c; 要干的活越多&#xff0c; 还要盯盘&#xff0c; 效率得提升一下。 总结下平时拉线容易用到的快捷键、评审资料等。 以后不断更新完善。 公众号发消息&#xff08;Download|拉线援&#xff09;可获得源文档。 一、Altium Desi…

水文气象站远程监测物联网系统,彰显水利治理智慧

我国在几千年的历史中都对水利设施建设十分重视&#xff0c;修建了各种类型的水利设施&#xff0c;在古代的生活、农业生产、防洪排水、漕运等多个方面发挥着非常重要的作用&#xff0c;如都江堰、郑国渠、坎儿井等设施更是青史留名。 到了现代&#xff0c;水资源治理依旧是民生…

开源SCRM营销平台-MarketGo产品介绍(二)

1、MarketGo概述 MarketGo中国式营销自动化开源项目标杆。 MarketGo更像是一个 SDK 、引擎&#xff0c;通过提供的标准化功能和基础能力&#xff0c;让开发者能快速搭建一个营销自动化系统&#xff0c;快速完成从0-1的过程&#xff0c;并且能基于开放的能力和源码&#xff0c…

爱了、阿里巴巴 JAVA 岗发布,最新内部面试题(含 P5-P7)

不少人对阿里巴巴技术岗的体系结构及级别的技术要求设置不太清楚&#xff0c;想去面试心里没底&#xff0c;下面简单介绍一下阿里 P5-P7 技术岗要求体系以及为大家分享一份涵盖阿里巴巴 P5-P7 的完整面试题&#xff01; 阿里 P5(高级研发工程师) 工作要求&#xff1a; 能独立…

容器的常用方法和线程安全(Map、List、Queue)

一、Map 1. HashTable 线程安全的Map&#xff0c;用synchronized锁 2. Collections.synchronizedMap Collections.synchronizedMap(new HashMap()) 可以把HashMap变成线程安全的&#xff0c;锁粒度比HashTable稍微小一点 3. ConcurrentHashMap ConcurrentHashMap主要提高…

【C++】STL - vector使用和模拟实现

&#x1f431;作者&#xff1a;傻响 &#x1f431;专栏&#xff1a;《C/C - STL》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; ​ 目录 No.1.vector的介绍及使用 1.1 vector的介绍 1.2 vector的接口 vector的构造函数接口 vector的…

最近很火的电视剧《点燃我,温暖你》男主角学神和女主角课代表计算机考试实现的跳动的爱心,全程用代码复原真实的实现

最近很火的电视剧《点燃我&#xff0c;温暖你》男主角学神和女主角课代表计算机考试实现的跳动的爱心&#xff0c;全程用代码复原真实的实现。 学神考试100分&#xff0c;只是因为试卷只有100分&#xff01; 该剧改编自作家Twentine创作的小说《打火机与公主裙》&#xff0c;讲…

微服务框架 SpringCloud微服务架构 微服务保护 31 限流规则 31.5 流控效果【排队等待】

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 微服务保护 文章目录微服务框架微服务保护31 限流规则31.5 流控效果【排队等待】31.5.1 流控效果【排队等待】31.5.2 案例31.5.3 总结31 限流…

JAVA物业管理系统带小程序源码

源码分享&#xff01;文末卡片查看联系方式获取源码。 基于Web&#xff0c;使用MySQL数据库&#xff0c;使用安全框架&#xff1a;shiro, 使用技术&#xff1a;springspringMVCMybatis&#xff0c;小程序 前端框架&#xff1a;layui 编译器&#xff1a;IntelliJ IDEA 项目构…

Linux消息中间件-RabbitMQ

Linux消息中间件-RabbitMQ 消息中间件 MQ简介 MQ 全称为Message Queue, 消息队列。是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息&#xff08;针对应用程序的数据&#xff09;来通信&#xff0c;而无需专用连接来链接它们。消息传递指的是程序之间通…

Python字符串关键点分析介绍

Python字符串关键点有下面几点&#xff1a; 1.一些引号分隔的字符 你可以把字符串看出是Python的一种数据类型&#xff0c;在Python单引号或者双引号之间的字符数组或者连续的字符集合。在python中最常用的引号为&#xff08;’’&#xff09;和("")。两者的功能是一…