我是靠谱客的博主 直率过客,最近开发中收集的这篇文章主要介绍Android定时任务1. Timer2. ScheduledExecutorService3. DelayQueue 实现延迟任务4. Sleep休眠大法,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1. Timer

Timer是 JDK 自带的定时任务执行类,可以直接使用 Timer来实现定时任务。这种实现方式比较简单,可以指定首次执行的延迟时间、首次执行的具体日期时间,以及执行频率,能满足日常需要。Timer是线程安全的,因为背后是单线程在执行所有任务。Timer是基于绝对时间调度的,而不是基于相对时间,所以它对系统时间的改变非常敏感。

1.1 使用:

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 测试定时任务
 */
public class Test {

    public static void main(String[] args) {
        // 定义一个任务
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Run timerTask:" + new Date());
            }
        };
        // 计时器
        Timer timer = new Timer();
        // 添加执行任务(延迟 1s 执行,每 2s 执行一次)
        timer.schedule(timerTask, 1000, 2000);
    }
}

几个重要的方法:

  • schedule():开始调度任务,提供了几个包装方法;
  • cancle():终止任务调度,取消当前调度的所有任务,正在运行的任务不受影响;
  • purge():从任务队列中移除所有已取消的任务;

另外,java.util.TimerTask就是实现了 Runnable接口,具体任务逻辑则是在 run()方法里去实现。

public abstract class TimerTask implements Runnable {
	// ......
}

执行结果如下:

Run timerTask:Wed Mar 17 10:42:05 CST 2021
Run timerTask:Wed Mar 17 10:42:07 CST 2021
Run timerTask:Wed Mar 17 10:42:09 CST 2021
Run timerTask:Wed Mar 17 10:42:11 CST 2021
Run timerTask:Wed Mar 17 10:42:13 CST 2021
Run timerTask:Wed Mar 17 10:42:15 CST 2021
Run timerTask:Wed Mar 17 10:42:17 CST 2021
Run timerTask:Wed Mar 17 10:42:19 CST 2021
Run timerTask:Wed Mar 17 10:42:21 CST 2021
......

1.2 Timer 缺点分析:

问题 1:任务执行时间长影响其他任务。

开启两个线程,如下:

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

/**
 * 测试定时任务
 */
public class Test {

    public static void main(String[] args) {
        // 定义任务 1
        TimerTask timerTask1 = new TimerTask() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(3);// 休眠 3 秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Run timerTask 1:" + new Date());
            }
        };
        // 定义任务 2
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Run timerTask 2:" + new Date());
            }
        };

        // 计时器
        Timer timer = new Timer();
        // 添加执行任务(延迟 1s 执行,每 3s 执行一次)
        timer.schedule(timerTask1, 1000, 3000);
        timer.schedule(timerTask2, 1000, 3000);
    }
}

执行结果如下:

Run timerTask 1:Wed Mar 17 10:50:54 CST 2021
Run timerTask 2:Wed Mar 17 10:50:54 CST 2021
Run timerTask 1:Wed Mar 17 10:50:57 CST 2021
Run timerTask 2:Wed Mar 17 10:50:57 CST 2021
Run timerTask 1:Wed Mar 17 10:51:00 CST 2021
Run timerTask 1:Wed Mar 17 10:51:03 CST 2021
Run timerTask 2:Wed Mar 17 10:51:03 CST 2021
Run timerTask 1:Wed Mar 17 10:51:06 CST 2021
Run timerTask 1:Wed Mar 17 10:51:09 CST 2021
Run timerTask 2:Wed Mar 17 10:51:09 CST 2021
Run timerTask 1:Wed Mar 17 10:51:12 CST 2021
Run timerTask 1:Wed Mar 17 10:51:15 CST 2021
Run timerTask 2:Wed Mar 17 10:51:15 CST 2021
Run timerTask 1:Wed Mar 17 10:51:18 CST 2021
Run timerTask 1:Wed Mar 17 10:51:21 CST 2021
Run timerTask 2:Wed Mar 17 10:51:21 CST 2021
Run timerTask 1:Wed Mar 17 10:51:24 CST 2021
Run timerTask 1:Wed Mar 17 10:51:27 CST 2021
Run timerTask 2:Wed Mar 17 10:51:27 CST 2021
Run timerTask 1:Wed Mar 17 10:51:30 CST 2021

看到执行结果,走着走着时间就不准了,如果开启更多会更乱。Timer是单线程的,假如有任务 A、B、C,任务 A 如果执行时间比较长,那么就会影响任务 B、C 的启动和执行时间,如果 B、C 执行时间也比较长,那就会相互影响。

问题 2:任务异常影响其他任务

public static void main(String[] args) {
        // 定义任务 1
        TimerTask timerTask1 = new TimerTask() {
            @Override
            public void run() {
                /*try {
                    TimeUnit.SECONDS.sleep(3);// 休眠 3 秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/

                int num = 119 / 0;// 没困难创造一个困难
                System.out.println("Run timerTask 1:" + new Date());
            }
        };
        // 定义任务 2
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Run timerTask 2:" + new Date());
            }
        };

        // 计时器
        Timer timer = new Timer();
        // 添加执行任务(延迟 1s 执行,每 3s 执行一次)
        timer.schedule(timerTask1, 1000, 3000);
        timer.schedule(timerTask2, 1000, 3000);
    }

执行结果:

Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
	at com.stock.messenger.ui.fragment.Test$1.run(Test.java:24)
	at java.util.TimerThread.mainLoop(Timer.java:555)
	at java.util.TimerThread.run(Timer.java:505)

Process finished with exit code 0

一个任务出现异常,直接全部挂掉了,其他任务也无法继续了,说明Timer不会捕获异常,如果 A、B、C 任何一个任务在执行过程中发生异常,就会导致 Timer整个定时任务停止工作。

小结:

Timer类实现定时任务的优点是使用简单方便,因为它是 JDK 自定的定时任务,但缺点是任务如果执行时间太长或者是任务执行异常,会影响其他任务调度,在正式的生产环境下建议谨慎使用。所以,如果在使用 Timer的过程中要注意这些缺陷,虽然可以用,但不推荐。

2. ScheduledExecutorService

Timer有一些缺陷,所以不太建议使用 Timer,推荐使用 ScheduledExecutorServiceScheduledExecutorService也是 JDK 1.5 自带的 API,我们可以使用它来实现定时任务的功能,也就是说 ScheduledExecutorService可以实现 Timer类具备的所有功能,并且它可以解决了 Timer类存在的所有问题。ScheduledExecutorService即是 Timer的替代者,JDK 1.5 并发包引入,是基于线程池设计的定时任务类:

可以查看我的另一篇文章:Java/Android中ScheduledExecutorService定时器任务

java.util.concurrent.Executors.newScheduledThreadPool

上了线程池,每个调度任务都会分配到线程池中的某一个线程去执行,任务就是并发调度执行的,任务之间互不影响。

几个重要的调度方法:

  • schedule():只执行一次调度;
  • scheduleAtFixedRate():按固定频率调度,如果执行时间过长,下一次调度会延迟,不会同时执行;
  • scheduleWithFixedDelay():延迟调度,上一次执行完再加上延迟时间后执行。

任务是支持 RunnableCallable调度的。

使用:

public static void main(String[] args) {
    // 创建任务队列
    ScheduledExecutorService scheduledExecutorService =
            Executors.newScheduledThreadPool(4); // 4 为线程数量
    // 执行任务,可以以固定的频率一直执行任务
    scheduledExecutorService.scheduleAtFixedRate(() -> {
        System.out.println("Run Schedule:" + new Date());
    }, 0, 2, TimeUnit.SECONDS); // 0s 后开始执行,每 2s 执行一次
}

执行结果:

Run Schedule:Wed Mar 17 11:03:27 CST 2021
Run Schedule:Wed Mar 17 11:03:29 CST 2021
Run Schedule:Wed Mar 17 11:03:31 CST 2021
Run Schedule:Wed Mar 17 11:03:33 CST 2021
Run Schedule:Wed Mar 17 11:03:35 CST 2021
......

2.1 任务超时执行测试:

ScheduledExecutorService可以解决 Timer任务之间相应影响的缺点,首先来测试一个任务执行时间过长,会不会对其他任务造成影响,测试代码如下:

public static void main(String[] args) {
        // 创建任务队列
        ScheduledExecutorService scheduledExecutorService =
                Executors.newScheduledThreadPool(4);
        // 执行任务 1
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                // 休眠 5 秒
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Run Schedule1:" + new Date());
        }, 0, 3, TimeUnit.SECONDS); // 0s 后开始执行,每 3s 执行一次
        // 执行任务 2
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("Run Schedule2:" + new Date());
        }, 0, 3, TimeUnit.SECONDS); // 0s 后开始执行,每 3s 执行一次
    }

执行结果正常:

Run Schedule2:Wed Mar 17 11:05:24 CST 2021
Run Schedule2:Wed Mar 17 11:05:27 CST 2021
Run Schedule1:Wed Mar 17 11:05:29 CST 2021
Run Schedule2:Wed Mar 17 11:05:30 CST 2021
Run Schedule2:Wed Mar 17 11:05:33 CST 2021
Run Schedule1:Wed Mar 17 11:05:34 CST 2021
Run Schedule2:Wed Mar 17 11:05:36 CST 2021
Run Schedule2:Wed Mar 17 11:05:39 CST 2021
Run Schedule1:Wed Mar 17 11:05:39 CST 2021
......

从上述结果可以看出,当任务 1 执行时间 5s 超过了执行频率 3s 时,并没有影响任务 2 的正常执行,因此使用 ScheduledExecutorService可以避免任务执行时间过长对其他任务造成的影响。

2.2 任务异常测试

接下来我们来测试一下 ScheduledExecutorService在一个任务异常时,是否会对其他任务造成影响,测试代码如下:

public static void main(String[] args) {
        // 创建任务队列
        ScheduledExecutorService scheduledExecutorService =
                Executors.newScheduledThreadPool(4);
        // 执行任务 1
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            int num = 119 / 0; // 没困难创造一个困难
            System.out.println("Run Schedule1:" + new Date());
        }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
        // 执行任务 2
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("Run Schedule2:" + new Date());
        }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
    }

结果如下:

Run Schedule2:Wed Mar 17 11:08:35 CST 2021
Run Schedule2:Wed Mar 17 11:08:38 CST 2021
Run Schedule2:Wed Mar 17 11:08:41 CST 2021
Run Schedule2:Wed Mar 17 11:08:44 CST 2021
Run Schedule2:Wed Mar 17 11:08:47 CST 2021
Run Schedule2:Wed Mar 17 11:08:50 CST 2021
......

从上述结果可以看出,当任务 1 死掉后,并不会影响任务 2 的执行。

小结

在单机生产环境下建议使用 ScheduledExecutorService来执行定时任务,它是 JDK 1.5 之后自带的 API,因此使用起来也比较方便,并且不会造成任务间的相互影响。

3. DelayQueue 实现延迟任务

DelayQueue是一个支持延时获取元素的无界阻塞队列,队列中的元素必须实现 Delayed 接口,并重写 getDelay(TimeUnit)compareTo(Delayed)方法,DelayQueue实现延迟队列的完整代码如下:

// 需要抛出异常
public static void main(String[] args) throws InterruptedException {
    DelayQueue delayQueue = new DelayQueue();
    // 添加延迟任务
    delayQueue.put(new DelayElement(1000));
    delayQueue.put(new DelayElement(3000));
    delayQueue.put(new DelayElement(5000));
    System.out.println("开始时间:" +  DateFormat.getDateTimeInstance().format(new Date()));
    while (!delayQueue.isEmpty()){
        // 执行延迟任务
        System.out.println(delayQueue.take());
    }
    System.out.println("结束时间:" +  DateFormat.getDateTimeInstance().format(new Date()));
}

static class DelayElement implements Delayed {
    // 延迟截止时间(单面:毫秒)
    long delayTime = System.currentTimeMillis();

    public DelayElement(long delayTime) {
        this.delayTime = (this.delayTime + delayTime);
    }

    @Override
    // 获取剩余时间
    public long getDelay(TimeUnit unit) {
        return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    // 队列里元素的排序依据
    public int compareTo(Delayed o) {
        if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {
            return 1;
        } else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {
            return -1;
        } else {
            return 0;
        }
    }

    @Override
    public String toString() {
        return DateFormat.getDateTimeInstance().format(new Date(delayTime));
    }
}

以上程序执行的结果为:

开始时间:2021-3-17 11:14:22
2021-3-17 11:14:23
2021-3-17 11:14:25
2021-3-17 11:14:27
结束时间:2021-3-17 11:14:27

4. Sleep休眠大法

作为补充,最常用的 sleep休眠大法,不只是当作休眠用,我们还可以利用它很轻松的能实现一个简单的定时任务。

实现逻辑:

这种方式比较傻瓜化了,新开线程添加一个 for / while死循环,在里面添加sleep休眠逻辑,让程序每隔 n 秒休眠再执行一次,这样就达到了一个简单定时任务的效果。只能按固定频率运行,不能指定具体运行的时间。

实现代码如下:

public static void main(String[] args) {
    // 休眠实现定时任务
    new Thread(() -> {
        while (true) {
            System.out.println("hi, I'm sleep.");

            try {
                // 每隔2秒执行一次
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

最后

以上就是直率过客为你收集整理的Android定时任务1. Timer2. ScheduledExecutorService3. DelayQueue 实现延迟任务4. Sleep休眠大法的全部内容,希望文章能够帮你解决Android定时任务1. Timer2. ScheduledExecutorService3. DelayQueue 实现延迟任务4. Sleep休眠大法所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(56)

评论列表共有 0 条评论

立即
投稿
返回
顶部