定时任务的实现原理,看完就能手撸一个! |
您所在的位置:网站首页 › 批量任务是什么 › 定时任务的实现原理,看完就能手撸一个! |
一、摘要 在很多业务的系统中,我们常常需要定时的执行一些任务,例如定时发短信、定时变更数据、定时发起促销活动等等。 在上篇文章中,我们简单的介绍了定时任务的使用方式,不同的架构对应的解决方案也有所不同,总结起来主要分单机和分布式两大类,本文会重点分析下单机的定时任务实现原理以及优缺点,分布式框架的实现原理会在后续文章中进行分析。 从单机角度,定时任务实现主要有以下 3 种方案: while + sleep 组合最小堆实现时间轮实现二、while+sleep组合while+sleep 方案,简单的说,就是定义一个线程,然后 while 循环,通过 sleep 延迟时间来达到周期性调度任务。 简单示例如下: 代码语言:javascript复制public static void main(String[] args) { final long timeInterval = 5000; new Thread(new Runnable() { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + "每隔5秒执行一次"); try { Thread.sleep(timeInterval); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); }实现上非常简单,如果我们想在创建一个每隔3秒钟执行一次任务,怎么办呢? 同样的,也可以在创建一个线程,然后间隔性的调度方法;但是如果创建了大量这种类型的线程,这个时候会发现大量的定时任务线程在调度切换时性能消耗会非常大,而且整体效率低! 面对这种在情况,大佬们也想到了,于是想出了用一个线程将所有的定时任务存起来,事先排好序,按照一定的规则来调度,这样不就可以极大的减少每个线程的切换消耗吗? 正因此,JDK 中的 Timer 定时器由此诞生了! 三、最小堆实现所谓最小堆方案,正如我们上面所说的,每当有新任务加入的时候,会把需要即将要执行的任务排到前面,同时会有一个线程不断的轮询判断,如果当前某个任务已经到达执行时间点,就会立即执行,具体实现代表就是 JDK 中的 Timer 定时器! 3.1、Timer首先我们来一个简单的 Timer 定时器例子 代码语言:javascript复制public static void main(String[] args) { Timer timer = new Timer(); //每隔1秒调用一次 timer.schedule(new TimerTask() { @Override public void run() { System.out.println("test1"); } }, 1000, 1000); //每隔3秒调用一次 timer.schedule(new TimerTask() { @Override public void run() { System.out.println("test2"); } }, 3000, 3000); }实现上,好像跟我们上面介绍的 while+sleep 方案差不多,同样也是起一个TimerTask线程任务,只不过共用一个Timer调度器。 下面我们一起来打开源码看看里面到底有些啥! 进入Timer.schedule()方法从方法上可以看出,这里主要做参数验证,其中TimerTask是一个线程任务,delay表示延迟多久执行(单位毫秒),period表示多久执行一次(单位毫秒) 代码语言:javascript复制public void schedule(TimerTask task, long delay, long period) { if (delay < 0) throw new IllegalArgumentException("Negative delay."); if (period (Long.MAX_VALUE >> 1)) period >>= 1; synchronized(queue) { if (!thread.newTasksMayBeScheduled) throw new IllegalStateException("Timer already cancelled."); synchronized(task.lock) { if (task.state != TimerTask.VIRGIN) throw new IllegalStateException( "Task already scheduled or cancelled"); task.nextExecutionTime = time; task.period = period; task.state = TimerTask.SCHEDULED; } queue.add(task); if (queue.getMin() == task) queue.notify(); } } 我们继续来看queue对象任务会将入到TaskQueue队列中,同时在Timer初始化阶段会将TaskQueue作为参数传入到TimerThread线程中,并且起到线程 代码语言:javascript复制public class Timer { private final TaskQueue queue = new TaskQueue(); private final TimerThread thread = new TimerThread(queue); public Timer() { this("Timer-" + serialNumber()); } public Timer(String name) { thread.setName(name); thread.start(); } //... } 而TaskQueue其实是一个最小堆的数据实体类,源码如下每当有新元素加入的时候,会对原来的数组进行重排,会将即将要执行的任务排在数组的前面 代码语言:javascript复制class TaskQueue { private TimerTask[] queue = new TimerTask[128]; private int size = 0; void add(TimerTask task) { // Grow backing store if necessary if (size + 1 == queue.length) queue = Arrays.copyOf(queue, 2*queue.length); queue[++size] = task; fixUp(size); } private void fixUp(int k) { while (k > 1) { int j = k >> 1; if (queue[j].nextExecutionTime |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |