java实现定时任务的坑

48 阅读4分钟

总的来说,实现定时任务,大致有四种方式,timer,ScheduledThreadPool,spring的@Scheduled注解和Quartz框架。

注解基于spring框架,添加cron表达式内容即可,Quartz是框架,在这里暂时不做赘述。

详细比较timer和ScheduledThreadPool两种方式:

timer只支持单线程,ScheduledThreadPool可以支持多线程。

1、timer执行多个任务调度,有一个任务抛出异常,其他任务停止;ScheduledThreadPool则会继续执行别的任务:

time:

       Timer timer = new Timer("Time");

        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("程序开始于:" + new Date());
                throw new RuntimeException("Exception in task1");
            }
        };

        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("程序开始于:" + new Date());
            }
        };

        long delay = 0L;
        long period = 1000L;

        timer.scheduleAtFixedRate(task1,delay,period);
        timer.scheduleAtFixedRate(task2,delay + 500,period);

        // 主线程等待,以便观察效果
        try {
            Thread.sleep(5000); // 主线程等待5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        timer.cancel();

输出日志为:

程序开始于:Fri Jul 19 15:02:29 CST 2024
Exception in thread "Time" java.lang.RuntimeException: Exception in task1
	at com.wutongservice.user.controller.DemoTest$1.run(DemoTest.java:23)
	at java.util.TimerThread.mainLoop(Timer.java:555)
	at java.util.TimerThread.run(Timer.java:505)

ScheduledThreadPool

        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        Runnable task1 = () -> {
            System.out.println("程序1开始于:" + new Date());
            throw new RuntimeException("Exception in task1");
        };
        Runnable task2 = () -> {
            System.out.println("程序2开始于:" + new Date());

        };

        long initialDelay = 0;
        long period = 1;
        scheduler.scheduleAtFixedRate(task1,initialDelay,period, TimeUnit.MILLISECONDS);
        scheduler.scheduleAtFixedRate(task2,initialDelay + 500,period, TimeUnit.MILLISECONDS);

        // 主线程等待,以便观察效果
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        scheduler.shutdown();
    }

输出日志:

程序1开始于:Fri Jul 19 15:06:44 CST 2024
程序2开始于:Fri Jul 19 15:06:45 CST 2024
程序2开始于:Fri Jul 19 15:06:45 CST 2024
程序2开始于:Fri Jul 19 15:06:45 CST 2024
程序2开始于:Fri Jul 19 15:06:45 CST 2024
程序2开始于:Fri Jul 19 15:06:45 CST 2024
程序2开始于:Fri Jul 19 15:06:45 CST 2024
程序2开始于:Fri Jul 19 15:06:45 CST 2024
程序2开始于:Fri Jul 19 15:06:45 CST 2024

2、timer只支持单线程,一个任务执行完才会执行下一个任务;ScheduledThreadPool支持多线程,各个任务都是并发的执行。

timer

        Timer timer = new Timer("Time");

        //模拟一个长时间任务
        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task1开始于:" + new Date());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };

        //正常的打印任务
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task2开始于:" + new Date());
            }
        };

        long delay = 0L;
        long period = 1000L;

        timer.scheduleAtFixedRate(task1,delay,period);
        timer.scheduleAtFixedRate(task2,delay + 500,period);

        // 主线程等待,以便观察效果
        try {
            Thread.sleep(10000); // 主线程等待10秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        timer.cancel();

输出日志:

task1开始于:Fri Jul 19 15:19:11 CST 2024
task2开始于:Fri Jul 19 15:19:14 CST 2024
task1开始于:Fri Jul 19 15:19:14 CST 2024
task2开始于:Fri Jul 19 15:19:17 CST 2024
task1开始于:Fri Jul 19 15:19:17 CST 2024
task2开始于:Fri Jul 19 15:19:20 CST 2024
task1开始于:Fri Jul 19 15:19:20 CST 2024

任务2在任务1执行完才执行

ScheduledThreadPool:

        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
        //模拟一个长时间任务
        Runnable task1 = () -> {
            System.out.println("程序1开始于:" + new Date());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("程序1结束于:" + new Date());
        };

        //正常的打印任务
        Runnable task2 = () -> {
            System.out.println("程序2开始于:" + new Date());

        };

        long initialDelay = 0;
        long period = 1;
        scheduler.scheduleAtFixedRate(task1,initialDelay,period, TimeUnit.MILLISECONDS);
        scheduler.scheduleAtFixedRate(task2,initialDelay + 500,period, TimeUnit.MILLISECONDS);

        // 主线程等待,以便观察效果
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        scheduler.shutdown();

输出日志:

程序1开始于:Fri Jul 19 15:28:38 CST 2024
程序2开始于:Fri Jul 19 15:28:39 CST 2024
程序2开始于:Fri Jul 19 15:28:39 CST 2024
程序2开始于:Fri Jul 19 15:28:39 CST 2024
程序2开始于:Fri Jul 19 15:28:39 CST 2024
程序2开始于:Fri Jul 19 15:28:39 CST 2024
程序2开始于:Fri Jul 19 15:28:39 CST 2024
程序2开始于:Fri Jul 19 15:28:39 CST 2024
程序2开始于:Fri Jul 19 15:28:39 CST 2024

程序2的执行不受第一个任务的影响。

总结来说,当执行多个定时任务时,使用ScheduledThreadPool效果远远优于使用timer。