1. 定时任务实现方式对比

1.1. Timer

  • 代码例子如下
    public static void main(String[] args) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime localDateTime = LocalDateTime.now();
        String format = localDateTime.format(formatter);
        System.out.println("1:"+format);
        Timer timer = new Timer();
        for (int i = 0; i < 2; i++) {
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread name : "+ Thread.currentThread().getName());
                    LocalDateTime localDateTime = LocalDateTime.now();
                    String format = localDateTime.format(formatter);
                    System.out.println("2:"+format);
                }
            }, 3000);
        }

        localDateTime = LocalDateTime.now();
        format = localDateTime.format(formatter);
        System.out.println("3:"+format);
    }

结果

1:2019-10-14 17:35:13
3:2019-10-14 17:35:13
Thread name : Timer-0
2:2019-10-14 17:35:19
Thread name : Timer-0
2:2019-10-14 17:35:22
  • 可以看出同一个Timer的定时任务,后台就一个线程管理任务分配,遇到任务阻塞,可能导致下一个任务延迟
  • 且如果任务发生异常,系统就终止了

1.2. ScheduledExecutorService

  • 为了解决Timer的问题,ScheduledExecutorService做了改进,采用了线程池的定时任务队列,实际使用的也是最小堆排序
  • 代码如下
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) {
//        timerTest();
        print("1:");
        ScheduledExecutorService service = new ScheduledThreadPoolExecutor(2);
        for (int i = 0; i < 2; i++) {
            service.schedule(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread name : " + Thread.currentThread().getName());
                    print("2:");
                }
            }, 3, TimeUnit.SECONDS);
        }
        print("3:");
        service.shutdown();
    }

    private static void print(String s) {
        LocalDateTime localDateTime = LocalDateTime.now();
        String format = localDateTime.format(formatter);
        System.out.println(s + format);
    }

结果

1:2019-10-15 11:53:54
3:2019-10-15 11:53:54
Thread name : pool-1-thread-1
2:2019-10-15 11:54:00
Thread name : pool-1-thread-2
2:2019-10-15 11:54:00

明白它的延迟原理和Timer一样,可以知道如果我把核心线程数改成1,则效果和Timer类似

  • 结果如下,两个任务延迟3秒,前一个任务会导致后一个任务延迟
1:2019-10-15 11:57:40
3:2019-10-15 11:57:40
Thread name : pool-1-thread-1
2:2019-10-15 11:57:46
Thread name : pool-1-thread-1
2:2019-10-15 11:57:49
  • 它的优势在于可以多线程执行,一定程度上避免任务间互相影响,同时单个任务异常不影响其它任务

1.3. 时间轮(延迟消息)

  • 本质是环形数组,比如上述8个节点的时间轮,每个节点存放任务队列,比如我们需要新建5s延迟的任务,就放5的节点,新建10s延迟的任务就放2的节点,设延迟时间为n,则节点位置为n%8,同时需记下轮询圈数n/8
  • 优势:当任务数量非常多的时候采用这样环形数组加队列是个效率比较高的选择
  • 想要了解更多时间轮实现,可以参考文章下的参考博客

1.4. 分布式定时任务

  • github上也有些开源的分布式定时任务的方案,可以直接使用
  • xxl_jobelastic-job-litelight-task-scheduler,以哪个或用哪个的原则,那还是xxl_job的星星最多,另外两个已经两年没更新了
01-19 11:13