任务调度实现

任务调度

定义:任务调度是基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。java

Timer

Timer 是jdk提供的一个定时器工具,会在主线程以外起一个单独线程执行指定的计划任务。数组

public class TimerTest extends TimerTask {

    private String jobName;

    public TimerTest(String jobName) {
        this.jobName = jobName;
    }

    @Override
    public void run() {
        System.out.println("执行:" + jobName);
    }

    public static void main(String[] args) {
        Timer timer = new Timer();
        long delay1 = 1 * 1000;
        long interval1 = 1000;

        //从如今开始1秒后每隔1秒执行一次job1
        timer.schedule(new TimerTest("job1"), delay1, interval1);

        long delay2 = 2 * 1000;
        long interval2 = 2000;
        // 从如今开始2秒钟以后,每隔2秒钟执行一次 job2
        timer.schedule(new TimerTest("job2"), delay2, interval2);

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终止执行
        timer.cancel();
    }
}

Timer 的优势在于简单易用,但因为全部任务都是由同一个线程来调度,所以全部任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到以后的任务并发

ScheduledExecutor

ScheduledExecutor 类则在自定义线程池的基础上增长了周期性执行任务的功能ide

ScheduledThreadPoolExecutor 线程池中的每个线程负责执行一个定时任务,相互之间互不干扰,并发执行,线程池会轮询任务状态,执行时间到来才会真正执行线程。函数

public class ScheduledExecutorTest implements Runnable {

    private String jobName;

    public ScheduledExecutorTest(String jobName) {
        this.jobName = jobName;
    }

    public void run() {
        System.out.println("Start: " + getDate());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("execute " + jobName);
        System.out.println("End: " + getDate());
    }
    
    //获取当前时间
    public String getDate() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
        return df.format(new Date());// new Date()为获取当前系统时间
    }

    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);

        long initialDelay1 = 1;
        long period1 = 2;
        //固定时间间隔
        service.scheduleAtFixedRate(new ScheduledExecutorTest("job1"),
                initialDelay1, period1, TimeUnit.SECONDS);

        long initialDelay2 = 1;
        long peroid2 = 3;
        //本次执行结束和下次开始执行时间保持在三秒
        //每次执行时间:executeTime + peroid executeTime可能会变化,可是peroid不变
        /*service.scheduleWithFixedDelay(new ScheduledExecutorTest("job2"), initialDelay2, peroid2, TimeUnit.SECONDS); */
    }
}

使用Executors.newScheduledThreadPool(10)建立一个核心线程数量为10的线程池。工具

方法区别:
1.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
参数:this

  • Runnable 须要执行的task
  • initialDelay 延迟至启动任务的时间
  • period 本次开始到下次开始的时间间隔

注:spa

  • 若本次job执行时间小于period,本次开始执行和下次开始之间时间间隔固定
  • 若本次job执行时间大于period,则本次开始时间和下次开始时间间隔为job执行时间
  • 每次执行时间 :executeTime < period ? peroid : executeTime
  • 不会由于上一个线程的调度失效延迟而受到影响

2.scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)
注:线程

  • 本次执行结束和下次开始执行时间保持在三秒
  • 每次执行时间:executeTime + peroid executeTime可能会变化,可是peroid不变
  • 受到上一个线程调度影响,可能会推迟本次任务调度的执行

实现原理:

ScheduledThreadPool建立的线程池底层使用的是DelayQueuecode

Delayed元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的Delayed元素。若是延迟都尚未期满,则队列没有头部,而且poll将返回null。

当一个元素的 getDelay(TimeUnit.NANOSECONDS)方法返回一个小于等于0的值时,将发生到期。即便没法使用take或poll移除未到期的元素,也不会将这些元素做为正常元素对待。例如,size方法同时返回到期和未到期元素的计数。此队列不容许使用null元素

ScheduledThreadPoolExecutor是把任务添加到DelayedWorkQueue中,而DelayedWorkQueue则是相似于DelayQueue,内部维护着一个以时间为前后顺序的优先级队列,使用compareTo()方法使用与DelayedWorkQueue队列对其元素ScheduledThreadPoolExecutor task进行排序

小结:

上面建立线程池建议使用ScheduledThreadPoolExecutor,能够更加精确控制。 构造函数其中一个参数为DelayedWorkQueue,内部经过一个RunnableScheduledFuture数组保存Runnable任务,默认初始长度为16,它是一个优先级队列 它根据这个任务的下次执行时间来比较大小,这样可以保证queue[0]位置上的元素是最近须要执行的任务。

相关文章
相关标签/搜索