ScheduledThreadPoolExecutor-并发编程(Java)

本文对比了Java中Timer和ScheduledThreadPoolExecutor实现定时任务的优缺点,展示了它们在延迟执行和异常处理上的不同。Timer任务串行执行,异常会中断后续任务;而ScheduledThreadPoolExecutor支持并发执行,异常不会阻塞任务,但需额外处理。同时,文章给出了定时任务应用的示例,演示了如何实现每周四4:00:00执行的任务。
摘要由CSDN通过智能技术生成

1、Timer

在认为调度线程池功能加入之前,可以使用java.util.Timer来实现定时功能。

  • 优点:简单易用

  • 缺点:由于所以认为都由同一个线程调度,因此所有任务都是串行执行

    • 前一个认为的延迟或者异常将会影响到之后的任务
  • 延迟示例:

    import lombok.extern.slf4j.Slf4j;
    
    import java.util.Timer;
    import java.util.TimerTask;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j(topic = "c.TestTimer01")
    public class TestTimer01 {
        public static void main(String[] args) {
            Timer timer = new Timer();
    
            TimerTask task1 = new TimerTask() {
                @Override
                public void run() {
                    log.debug("task 1...");
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
    
            TimerTask task2 = new TimerTask() {
                @Override
                public void run() {
                    log.debug("task 2...");
                }
            };
    
            timer.schedule(task1, 1000);
            timer.schedule(task2, 1000);
        }
    }
    // 输出
    2021-12-16 09:04:57.752 DEBUG [main] c.TestTimer01 - start...
    2021-12-16 09:04:58.769 DEBUG [Timer-0] c.TestTimer01 - task 1...
    2021-12-16 09:05:00.776 DEBUG [Timer-0] c.TestTimer01 - task 2...
    
    • 任务2并没有如预期的延迟1s后开始执行,而是等待任务1执行完毕在执行
  • 异常示例

    import lombok.extern.slf4j.Slf4j;
    
    import java.util.Timer;
    import java.util.TimerTask;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j(topic = "c.TestTimer02")
    public class TestTimer02 {
        public static void main(String[] args) {
            Timer timer = new Timer();
    
            TimerTask task1 = new TimerTask() {
                @Override
                public void run() {
                    log.debug("task 1...");
                    int i = 1 / 0;
                }
            };
    
            TimerTask task2 = new TimerTask() {
                @Override
                public void run() {
                    log.debug("task 2...");
                }
            };
            log.debug("start...");
            timer.schedule(task1, 1000);
            timer.schedule(task2, 1000);
        }
    }
    
    // 输出
    2021-12-16 09:07:13.722 DEBUG [main] c.TestTimer02 - start...
    2021-12-16 09:07:14.727 DEBUG [Timer-0] c.TestTimer02 - task 1...
    Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
    
    • 任务1异常,程序中断,并没有执行任务2

2、ScheduledThreadPoolExecutor

2.1、延迟执行任务

那么我们用ScheduledThreadPoolExecutor改造上面的示例,看看是否解决之前的问题?

import lombok.extern.slf4j.Slf4j;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.TestScheduledThreadPool01")
public class TestScheduledThreadPool01 {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        log.debug("start...");
        pool.schedule(() -> {
            log.debug("task 1...");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("task 1 continue...");
            int i = 1 / 0;
            log.debug("task 1 end...");
        }, 1, TimeUnit.SECONDS);

        pool.schedule(() -> {
            log.debug("task 2...");
        }, 1, TimeUnit.SECONDS);
    }
}
// 输出
2021-12-16 09:27:27.703 DEBUG [main] c.TestScheduledThreadPool01 - start...
2021-12-16 09:27:28.752 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool01 - task 1...
2021-12-16 09:27:28.752 DEBUG [pool-2-thread-2] c.TestScheduledThreadPool01 - task 2...
2021-12-16 09:27:30.756 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool01 - task 1 continue...

我们发现问题得到解决,注意:如果线程池线程数设置为1 那么任务换是会串行执行。

但是我们又发现,明明任务1会出现执行异常,我们没有处理异常,但是异常不见了,并且任务没有结束,那么如何处理线程池异常呢?

2.2、定时执行任务

  • scheduleAtFixedRate(任务, 初始延时, 执行周期, 时间单位):执行周期就是每隔多次时间执行任务

  • scheduleWithFixedDelay(任务, 初始延时, 任务间隔, 时间单位):任务间隔为上次执行任务到本次执行任务之间的时间差

  • 示例:

    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j(topic = "c.TestScheduledThreadPool02")
    public class TestScheduledThreadPool02 {
        public static void main(String[] args) {
            ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
            log.debug("start...");
            method1(pool);
    
        }
    
        public static void method1(ScheduledExecutorService pool) {
            pool.scheduleAtFixedRate(() -> {
                log.debug("task 1 running...");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, 1, 1, TimeUnit.SECONDS);
        }
    
        public static void method2(ScheduledExecutorService pool) {
    
            pool.scheduleWithFixedDelay(() -> {
                log.debug("task 2 running...");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, 1, 1, TimeUnit.SECONDS);
        }
    
    
    }
    
    // 方法1输出结果
    2021-12-16 09:46:05.269 DEBUG [main] c.TestScheduledThreadPool02 - start...
    2021-12-16 09:46:06.343 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool02 - task 1 running...
    2021-12-16 09:46:08.348 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool02 - task 1 running...
    2021-12-16 09:46:10.354 DEBUG [pool-2-thread-2] c.TestScheduledThreadPool02 - task 1 running...
    // 方法2输出结果
    2021-12-16 09:49:49.959 DEBUG [main] c.TestScheduledThreadPool02 - start...
    2021-12-16 09:49:51.015 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool02 - task 2 running...
    2021-12-16 09:49:54.024 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool02 - task 2 running...
    2021-12-16 09:49:57.033 DEBUG [pool-2-thread-2] c.TestScheduledThreadPool02 - task 2 running...
    
    • 可以发现方法1任务间隔为2s,方法2任务间隔为3s

3、线程池异常处理

  • 方式1:任务自身处理异常,用try…catch…处理异常

  • 方式2:如果自己不处理,可以借助Future对象处理

  • 示例:

    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.*;
    
    @Slf4j(topic = "c.TestScheduledThreadPool01")
    public class TestScheduledThreadPool03 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
            log.debug("start...");
            ScheduledFuture<?> future = pool.schedule(() -> {
                log.debug("task 1...");
                int i = 1 / 0;
                return true;
            }, 1, TimeUnit.SECONDS);
            System.out.println(future.get());
        }
    }
    // 输出
    2021-12-16 09:56:21.398 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool01 - task 1...
    Exception in thread "main" java.util.concurrent.ExecutionException: 
    

4、定时任务应用

需求:我们系统需要每周4 4:00:00点定时维护,那么该如何做呢?

分析:

  1. 我们使用scheduleAtFixedRate方法

  2. 可以确定是任务周期为1周,那么初始延迟和时间单位如何确定呢?

  3. 既然任务周期为1周,那么我们需要知道当前时间和周4的时间间隔,即初始延时

  4. 当前时间可能在本周的周4之前,也可能之后。

  5. 为方便时间计算,我们用LocalDateTime类,既然要计算时间,那么单位为毫秒

代码:

import lombok.extern.slf4j.Slf4j;

import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 定期维护:每周4 4:00:00执行维护工作
 * @author Administrator
 */
@Slf4j(topic = "c.RegularMaintenance")
public class RegularMaintenance {
    public static void main(String[] args) {
        // 1、获取当前时间
        LocalDateTime now = LocalDateTime.now();
        // 2、确定第一个周4的时间
        LocalDateTime time = now.withHour(4).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
        // 如果当前时间晚于本周4 4:00:00,那么计算得出的时间在延后1周即可
        if (now.compareTo(time) > 0) {
            time = time.plusWeeks(1);
        }
        // 3、执行定时任务
        long initialDelay = Duration.between(now, time).toMillis();
        long period = 7 * 24 * 3600 * 1000;
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.scheduleAtFixedRate(() -> {
            log.debug("定时维护任务开始执行...");
            log.debug("定时维护任务结束...");
        }, initialDelay, period, TimeUnit.MILLISECONDS);
    }
}

参考视频:https://www.bilibili.com/video/BV16J411h7Rd 224~229

源代码仓库地址:https://gitee.com/gaogzhen/concurrent

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值