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

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

在《【自省】使用Executors.xxx违反阿里Java代码规范,那还不写定时任务了?》 中仍然通过【自省】的方式讨论了也可以使用 ScheduledExecutorService#scheduleAtFixedRate来实现定时任务,它的运行机制大概是这样:

二、理还乱?

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

boolean lockResult = lockInstance.tryLock();

if(lockResult){
    //do work
}finally{
    lockInstance.unLock();
}
  • 万一程序异常崩了,没执行finally呢?
  • 如果程序异常崩了,进程消失后,进程内的资源自然就都释放掉了:续期任务没有了,续期的线程|线程池也没有了。但锁资源就需要依赖锁服务,如 Redis ,在锁过期后主动释放掉锁资源。
  • 问题:关于停止任务,在前文独立线程的实现方式中,有介绍可通过中断机制;但是线程池里的任务怎么取消呢?
  • 遇事不决问百度,排名第一必有解
  • 咱得本意是取消一个任务,示例给出的方法是要把线程池关掉。
  • 问题:取消一个任务,要把整个线程池都关掉?按照示例所给的办法是不行的,每个任务的取消,都要关闭整个线程池的话,若给每个任务都配有独立的取消能力,就需要给每个任务都配一个独立的线程池,这就跟每个锁配一个独立的线程没有区别了。

  • 问题:目标是多个任务共享一个线程池,怎么不关闭线程池而只关闭特定的任务呢?百度出来跟问题相关的文章本就不多,而多数文章提供的奇思妙招并不好使,笔者是浪费了一些时间的,但不能再耽误读者朋友的时间,直接给思路:解铃还须系铃人,scheduleAtFixedRate的返回值是是ScheduledFuture。

  • 问题:看到 xxxFuture 是否想能想起Future接口的能力?猜测熟悉 get()方法的同学应该特别多,但不知道熟不熟悉cancel方法,如果看到这个方法感到惊喜,欢迎留言互动。
  • public interface Future { 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的参数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.

    问题:那就是说也可能抛出 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. 任务逻辑中的异常,需在任务逻辑内捕获并记录,否则无处可知。
    展开阅读全文

    页面更新:2024-03-30

    标签:线程   分布式   示例   逻辑   异常   主动   独立   方式   时间   方法

    1 2 3 4 5

    上滑加载更多 ↓
    推荐阅读:
    友情链接:
    更多:

    本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

    © CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
    闽公网安备35020302034903号

    Top