【自省】线程池里的定时任务跑的可欢了,可咋停掉特定的任务?

news2024/11/19 15:13:55
  • 客户端抢到分布式锁之后开始执行任务,执行完毕后再释放分布式锁。
  • 持锁后因客户端异常未能把锁释放,会导致锁成为永恒锁。
  • 为了避免这种情况,在创建锁的时候给锁指定一个过期时间。
  • 到期之后锁会被自动删除掉,这个角度看是对锁资源的一种保护。
  • 重点:但若锁过期被删除后,任务还没结束怎么办?
  • 可以通过在一个额外的线程中主动推迟分布式锁的过期时间,下文也用续期一词来表述;避免当任务还没执行完,锁就被删除了。
  • 但当分布式锁很多的情况下,每个锁都配置一个线程着实浪费,所以是否可以用线程池里的定时任务呢?

使用 ScheduledExecutorService#scheduleAtFixedRate来实现定时任务,它的运行机制大概是这样:

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

在分布式锁主动续期的场景下,它也满足如下定律:

二、理还乱?

ScheduledExecutorService#scheduleAtFixedRate逻辑看很简单,也很清晰,但任何事情都有两面性,把任务丢给线程池的方式,实现起来自然简单清晰,但肯定也有弊端。如果要把锁的功能做的健壮,总要从不断地自我质疑、自我反思中,理顺思路,寻找答案,我认为这属于自省式学习,以后也想尝试这种模式,一起再看看有啥问题:

  • 问题:锁主动释放的时候,续期的任务要关闭嘛?

    是的,当锁被用户主动关闭的时候,主动续期的任务是要主动取消掉的。

  • 问题:如果我不主动取消呢?

    对于不主动续期的锁,抢锁后配置一个合适的过期时间,到期之后锁自然会被释放;这种情况下,客户端本就没有续期任务需要取消。但如果有额外的线程|线程池在定时续期的话,锁用完了需要被释放掉,任务一定要主动取消掉。

  • 问题:可万一忘记了呢?

    有加锁解锁的代码模板,按照模板来;获取锁之后,在finally中执行释放锁的操作。

    boolean lockResult = lockInstance.tryLock();
    
    if(lockResult){
        //do work
    }finally{
        lockInstance.unLock();
    }
    复制代码
  • 万一程序异常崩了,没执行finally呢?

    如果程序异常崩了,进程消失后,进程内的资源自然就都释放掉了:续期任务没有了,续期的线程|线程池也没有了。但锁资源就需要依赖锁服务,如 Redis ,在锁过期后主动释放掉锁资源。

  • 问题:关于停止任务,在前文独立线程的实现方式中,有介绍可通过中断机制;但是线程池里的任务怎么取消呢?

    遇事不决问百度,排名第一必有解

    咱得本意是取消一个任务,示例给出的方法是要把线程池关掉。

  • 问题:取消一个任务,要把整个线程池都关掉?

    按照示例所给的办法是不行的,每个任务的取消,都要关闭整个线程池的话,若给每个任务都配有独立的取消能力,就需要给每个任务都配一个独立的线程池,这就跟每个锁配一个独立的线程没有区别了。

  • 问题:目标是多个任务共享一个线程池,怎么不关闭线程池而只关闭特定的任务呢?

    百度出来跟问题相关的文章本就不多,而多数文章提供的奇思妙招并不好使,笔者是浪费了一些时间的,但不能再耽误读者朋友的时间,直接给思路:解铃还须系铃人,scheduleAtFixedRate的返回值是是ScheduledFuture

  • 问题:看到 xxxFuture 是否想能想起Future接口的能力?

    猜测熟悉 get()方法的同学应该特别多,但不知道熟不熟悉cancel方法,如果看到这个方法感到惊喜,欢迎留言互动。

    public interface Future<V> {
        boolean cancel(boolean mayInterruptIfRunning);
        ...
        V get() throws InterruptedException, ExecutionException;
        ...
    }
    复制代码
  • 问题:cancel方法好使嘛?

    不看理论看实效果,试试看:

    public static void testCancel() throws InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
    
        System.out.println(" start : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("  work : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        }, 5, 5, TimeUnit.SECONDS);
    
        TimeUnit.SECONDS.sleep(15);
        scheduledFuture.cancel(true);
        System.out.println("cancel : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        TimeUnit.SECONDS.sleep(30);
    }
    复制代码

    效果满足预期,成功取消了。

     start : 2022-12-10T19:24:31.508
      work : 2022-12-10T19:24:36.538
      work : 2022-12-10T19:24:41.539
      work : 2022-12-10T19:24:46.541
    cancel : 2022-12-10T19:24:46.541 //成功取消
    复制代码

  • 问题:cancel 里都做了什么呢?

    看源码可知,其内有两层核心逻辑:

    • 尝试取消正在执行的任务
    • 避免任务再被定时执行
    public boolean cancel(boolean mayInterruptIfRunning) {
        // 1. 先调用父类FutureTask#cancel来取消任务。
        boolean cancelled = super.cancel(mayInterruptIfRunning);
        //2. 核心逻辑是从队列中删除该任务。
        if (cancelled && removeOnCancel && heapIndex >= 0)
            remove(this);
        return cancelled;
    }
    复制代码

至此,关于使用线程池来执行|取消续期任务,看起来已经没啥问题了;美丽的心情应该是这样的。

OK,稍微开心一下就好,还有问题呢,不想划开的,咱不等了。

三、新的思考

  • 问题:cancel的参数mayInterruptIfRunning 是什么意思?

    从父类cancel方法的注释中可以寻找到答案,如果是 true 的话,即代表尝试通过中断的方式来停止任务

    If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        if (RESULT_UPDATER.compareAndSet(this, null, CANCELLATION_CAUSE_HOLDER)) {
            if (checkNotifyWaiters()) {
                notifyListeners();
            }
            return true;
        }
        return false;
    }
    复制代码
  • 问题:那就是说也可能抛出 InterruptedException 了?

    如果是抛出 InterruptedException ,示例中,并未看到程序测试有异常中断,也未看到有异常日志信息。

  • 问题:怎么有点玄学了,还能不是interrupt机制?

    在任务内尝试捕获一下看看:

    public static void testExceptionCatch() throws InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        ScheduledFuture<?> scheduledFuture = null;
        System.out.println(" start : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        try {
            scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
                System.out.println("  work : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //throw new RuntimeException("");
            }, 5, 5, TimeUnit.SECONDS);
        }catch (Exception exp){
            exp.printStackTrace();
        }
        TimeUnit.SECONDS.sleep(15);
        scheduledFuture.cancel(true);
        System.out.println("cancel : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        TimeUnit.SECONDS.sleep(30);
    }
    
    复制代码

    结果中的信息 java.lang.InterruptedException: sleep interrupted 可以明确是任务内的逻辑是可通过中断机制实现的。

    start : 2022-12-10T20:10:31.248
      work : 2022-12-10T20:10:36.276
      work : 2022-12-10T20:10:41.272
      work : 2022-12-10T20:10:46.277
    cancel : 2022-12-10T20:10:46.277
    java.lang.InterruptedException: sleep interrupted
            at java.lang.Thread.sleep(Native Method)
            at java.lang.Thread.sleep(Thread.java:340)
            at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
            at com.wushiyii.lock.ScheduleTest.lambda$testExceptionCatch$1(ScheduleTest.java:39)
            at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
            at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
            at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
            at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
            at java.lang.Thread.run(Thread.java:748)
    
    复制代码
  • 问题:之前实例中取消任务时,外部也无异常信息,线程池内部留着这个异常干嘛了呢?

    直接抛出异常试试看

    public static void testExceptionCatch() throws InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        ScheduledFuture<?> scheduledFuture = null;
        System.out.println(" start : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        try {
            scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
                System.out.println("  work : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
                throw new RuntimeException("just throw ");
                //throw new RuntimeException("");
            }, 5, 5, TimeUnit.SECONDS);
        }catch (Exception exp){
            exp.printStackTrace();
        }
        TimeUnit.SECONDS.sleep(15);
        scheduledFuture.cancel(true);
        System.out.println("cancel : " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        TimeUnit.SECONDS.sleep(30);
    }
    复制代码

    仔细观察能看出,结果变的有意思了,work只执行了一次,前文中的执行结果中work都执行了3次,这里却只执行了一次。

     start : 2022-12-10T20:16:53.285
      work : 2022-12-10T20:16:58.307
    cancel : 2022-12-10T20:17:08.305
    复制代码
  • 问题:任务内抛出异常能导致定时任务失去定时执行的能力?

    是的,使用scheduleAtFixedRate有以下几个情况必须注意:

    1. 任务逻辑中未捕获的异常能导致本该定时执行的任务,后续不再执行。
    2. 任务逻辑中未捕获的异常不会外抛,外部感知不到。
    3. 任务逻辑中的异常,需在任务逻辑内捕获并记录,否则无处可知。

    看起来定时任务的使用的确是不能随心所欲的,毕竟大美也总是会说:

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

    给线程池指定的线程数要合理,不要无限制的提交任务,也不要每提交一个任务就new一个线程池。还有...

    老板亲情提示:面都坨了,快点吃,不然不好吃了。

    嗯,【自省】也不能饿肚子,该吃饭了。

    如果您还知道一些需要注意的玄妙机制,欢迎留言讨论;咱们下一篇再聊。


四、最后说一句

如果这篇文章对您有帮助,或者有所启发的话,欢迎进行交流和学习。您的支持是我坚持写作最大的动力。

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

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

相关文章

Going Home(二分图最大权匹配KM算法)

C-Going Home_2022图论班第一章图匹配例题与习题 (nowcoder.com) 在网格地图上有n个小人和n座房子。在每个单位时间内&#xff0c;每个小人都可以水平或垂直地移动一个单位步到相邻点。对于每个小矮人&#xff0c;你需要为他每走一步支付1美元的旅费&#xff0c;直到他进入一所…

Git命令笔记,下载、提交代码、解决冲突、分支处理

下载代码&#xff0c;复制https地址到本地文件夹&#xff0c;鼠标右键选择git bash后输入命令 git clone https://gitee.com/View12138/ViewFaceCore.git 下载后初始化&#xff1a;git init 下载代码后不运行报错&#xff08;如下&#xff09;&#xff0c;需要执行初始化命令…

Google ProtoBuf的使用

Google的protobuf太好用了&#xff0c;又小&#xff0c;读写又快 跑步快慢受鞋的影响太大了&#xff0c;但是造鞋的工具研究起来还是很有难度的&#xff0c;百度真是充斥的大量的转载文件&#xff0c;不管能不能用、能不能看懂&#xff0c;反正是各种转载&#xff0c;有的连错…

2023年企业固定资产管理怎么破局?

2022年已经在风雨中过去&#xff0c;转眼我们迎来了2023年。过去的一年&#xff0c;固定资产管理的痛依旧历历在目&#xff0c;如何让新的一年中&#xff0c;固定资产管理工作有所突破&#xff0c;不再承受固定资产资产管理的痛处&#xff0c;是每个企业管理者和企业固定资产管…

snap打包初步了解

前言 和snap比较类似的有三种打包方式&#xff1a; Snap Flatpak appimage Appimage是将所有的资源打包在一起&#xff0c;以一个类似与独立exe的方式执行&#xff0c;虽然简单使用&#xff0c;但是解压资源和本地缓存数据都比较麻烦。 Flatpak和snap十分类似&#xff0c;但…

XXE无回显攻击详解

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

怎么把element的tootip设置为点击后出现提示框,且在提示框里面放其他元素,vue2动态给对象添加属性并实现响应式应答,样式穿透

怎么把element的tootip设置为点击后出现提示框 我目前有一个需求&#xff0c;就是要点击文字才会出现提示框&#xff0c;而不是hover上去就以后&#xff0c;找资料看文档&#xff0c;看了半天让我终于实现了&#xff0c;其实也不难&#xff0c;可能是最开始我没有理解value&am…

Akka 进阶(一)Dispatcher调度器

目录一 Dispatcher 任务分发1.1 理解什么是Dispatcher1.2 Executor的分类1.3 基本使用1.4 其他类型的调度器在Akka中&#xff0c;Actor的消息通信和任务执行建立在一个完全透明的调度机制之上&#xff0c;它屏蔽了底层线程&#xff08;池&#xff09;的实现细节&#xff0c;几乎…

Java: static,final,代码块 的详解

Java: static&#xff0c;final&#xff0c;代码块 的详解 每博一文案 山本文绪说过这样一句话&#xff1a;哪些决定放弃了的事&#xff0c;就请放弃得干干净净。哪些决定再也不见面的人&#xff0c;就真 的不要再见面了&#xff0c;不要再做背叛自己的事&#xff0c;如果想要…

CentOS7迷你版安装Redis并配置基础信息

1. 安装gcc、wget依赖 yum install gcc yum install wget 2. 使用命令&#xff1a;wget http://download.redis.io/releases/redis-6.2.5.tar.gz 下载安装包&#xff0c;注意要先cd到要下载到的目标位置&#xff09; 3. tar -zxvf redis-6.2.5.tar.gz 解压压缩包 4. cd redis-…

学习周报-20221223

文章目录一 Linux的ACL访问控制列表一 基础概念1.1 起因1.2 系统支持1.3 相关定义二 查看ACL权限2.1 简单查看2.2 详细查看2.3 具体配置三 更改ACL权限3.1 添加或修改ACL3.2 输出和输入3.3 设置ACL掩码3.4 递归修改ACL3.5 删除ACL3.6 控制默认ACL权限二 Linux磁盘分区中物理卷&…

zabbix6.0安装教程(七):从web界面安装

zabbix6.0安装教程&#xff08;七&#xff09;&#xff1a;从web界面安装 目录一、欢迎主界面二、先决条件检查三、配置数据库连通性四、配置本章节提供有关Zabbx Web界面的部署步骤说明。Zabbix 前端是由PHP语言编写&#xff0c;所以其网页服务的运行需要支持PHP语言的网站服务…

Allegro如何任意角度走线操作指导

Allegro如何任意角度走线操作指导 Allegro支持在PCB上进行任意角度走线,尤其是在高速设计的时候,尤为常见,如下图 具体操作如下 选择add connect命令Find选择Cline segs

谷歌要给移动VR定规矩

代号牛轧糖的最新安卓7.0手机操作系统发布两天后&#xff0c;谷歌公司开发的虚拟现实&#xff08;VR&#xff09;平台Daydream&#xff08;下称“白日梦”&#xff09;也将在未来几周推出。与平台一同推出的&#xff0c;还有来自Hulu和YouTube等视频网站“网红”们的全新视频内…

【软件工程】实验3:软件详细设计

CAM系统的类图 CAM系统的顺序图 CAM系统模拟实现&#xff08;CAM工具库封装&#xff09; 1、代码运行界面 &#xff08;1&#xff09;根据提示输入要加工的图形类型 &#xff08;2&#xff09;J-Soft软件会根据输入的数字提示用户输入不同的图形参数&#xff0c;图形参数输入…

函数栈帧(栈区)

函数栈帧&#xff08;栈区&#xff09;一.前言二.main函数空间的开辟&#xff08;函数调用是如何做到的&#xff09;三.main函数内部的变量初始化&#xff08;局部变量是如何创建的以及为什么是随机值&#xff09;四.main函数内部的函数创建1.函数是如何传参的2.传参的顺序以及…

物联网通信技术原理第2章 无线通信技术(期末重点)

目录 2.1 无线通信基本知识 2.1.2 无线通信的特点 2.2 无线信道 2.2.1 恒参无线信道举例 2.2.2 恒参无线信道特性及其对信号传输的影响 1. 幅度—频率畸变 2. 相位—频率畸变​编辑 2.2.3 随参无线信道距离 1、短波电离层反射 2、对流层散射信道 2.2.4 随参信道特性及…

漏洞深度分析|Apache Karaf 4.2.16 存在JNDI 注入漏洞

项目地址 GitHub - apache/karaf: Mirror of Apache Karaf 项目介绍 Apache Karaf是一个模块化运行时&#xff0c;支持多种框架和编程模型&#xff08;REST/API、web、spring boot 等&#xff09;。它提供了统包功能&#xff0c;您可以毫不费力地直接利用这些功能&#xff0…

戴维南定理

定理内容&#xff1a;任意二端口直流网络都可以被一个等效电路替换&#xff0c;这个等效电路只包含一个电压源和一个串联电阻&#xff0c;如图1所示。 图1 戴维南定理的作用 1、分析含有非串联或非并联电源的网络 2、用最少数量的元件实现复杂网络的端口特性&#xff08;端口前…

【深入浅出Spring原理及实战】「开发实战系列」SpringSecurity原理以及实战认证分析开发指南

前提介绍 承接上一篇文章&#xff0c;相信大家应该已经对SpringSecurity的原理有了一定的认识&#xff0c;而本篇文章给大家带来的则是在实际业务开发中的技术指南&#xff0c;希望对你有所帮助。所谓知彼知己方能百战百胜&#xff0c;用Spring Security来满足我们的需求最好了…