java定时任务目前主要有三种:
Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行;而且作业类需要集成java.util.TimerTask,一般用的较少。
Spring3.0以后自带的task,即:spring schedule,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。
Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行;代码稍显复杂。
定时器算法 1.小顶堆堆,实际上是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于(或不小于)其左子节点和右子节点的值。
堆又分为两种,最大堆、最小堆。
最大堆: 任一非叶子节点的值均大于其左子节点和右子节点的值。根节点的值是最大的。最小堆: 任一非叶子节点的值均小于其左子节点和右子节点的值。根节点的值是最小的。 小顶堆的实现方式由于堆是一种经过排序的完全二叉树,因此在构建的时候需要对其新插入的节点进行一些操作以使其符合堆的性质。这种操作就叫上浮与下沉。
上浮:将当前节点与其父节点相比,如果当前节点的值比父节点小,就把当前节点与父节点交换,然后继续前面的交换,直到当前节点比父节点的值大为止。上浮就是将符合条件的节点往上移的过程。
下沉:将当前节点与其左、右子节点相比,如果当前节点的值比其中一个或两个子节点的值大,就把当前节点与两个子节点种比较小的那个交换,,然后继续前面的比较,直到当前节点的值比两个子节点的值都小为止。下沉就是将符合条件的节点往下移的过程。对于新手小白想更轻松的学好Java提升,Java架构,web开发、大数据,数据分析,人工智能等技术,这里给大家分享系统教学资源,扩列下我尉(同英):CGMX9880 【教程/工具/方法/解疑】
2.时间轮算法见名知意,时间轮算法的数据结构类似于钟表上的数据指针,时间轮用环形数组的方式实现,数组中的每个元素都可以称之为槽(和redis集群的槽一样称呼)。槽的内部用双向链表存储着待执行的任务,添加和删除链表的操作时间复杂度为O(1),槽位本身也指代时间精度,比如一秒扫一个槽,那么这个时间轮的最高精度就是1秒。
当有一个延迟任务要插入时间轮时,首先计算其延迟时间与单位时间的余值,从指针指向的当前槽位移动余值的个数槽位,就是该延迟任务需要被放入的槽位。
举个例子,时间轮有8个槽位,编号为 0 ~ 7 。指针当前指向槽位 2 。新增一个延迟时间为 4 秒的延迟任务,4 % 8 = 4,因此该任务会被插入 4 + 2 = 6,也就是槽位6的延迟任务队列。
时间槽位的实现方式时间轮的槽位实现可以采用循环数组的方式达成,也就是让指针在越过数组的边界后重新回到起始下标。概括来说,可以将时间轮的算法描述为:
用队列来存储延迟任务,同一个队列中的任务,其延迟时间相同。用循环数组的方式来存储元素,数组中的每一个元素都指向一个延迟任务队列。有一个当前指针指向数组中的某一个槽位,每间隔一个单位时间,指针就移动到下一个槽位。被指针指向的槽位的延迟队列,其中的延迟任务全部被触发。在时间轮中新增一个延迟任务,将其延迟时间除以单位时间得到的余值,从当前指针开始,移动余值对应个数的槽位,就是延迟任务被放入的槽位。
基于这样的数据结构,插入一个延迟任务的时间复杂度就下降到 O(1) 。而当指针指向到一个槽位时,该槽位连接的延迟任务队列中的延迟任务全部被触发。
延迟任务的触发和执行不应该影响指针向后移动的时间精确性。因此一般情况下,用于移动指针的线程只负责任务的触发,任务的执行交由其他的线程来完成。比如,可以将槽位上的延迟任务队列放入到额外的线程池中执行,然后在槽位上新建一个空白的新的延迟任务队列用于后续任务的添加。
代码实现 Timer `/** * @className: TimerTest * @description: 测试java.util.Timer的定时器实现 * @author: charon * @create: 2023-10-10 10:35 */public class TimerTest { public static void main(String[] args) { Timer timer = new Timer(); // 延迟1s执行任务 timer.schedule(new TimerTask() { @Override public void run() { System.out.println("延迟1s执行的任务"+new Date()); } },1000); // 延迟3s执行任务,每隔5s执行一次 timer.schedule(new TimerTask() { @Override public void run() { System.out.println("延迟3s每隔5s执行一次的任务"+new Date()); } },3000,5000); // try { // Thread.sleep(5000); // } catch (InterruptedException e) { // e.printStackTrace(); // } // timer.cancel(); // System.out.println("任务执行完毕"+new Date()); }}延迟1s执行的任务Sun Oct 10 14:34:13 CST 2023延迟3s执行的任务Sun Oct 10 14:34:15 CST 2023延迟3s执行的任务Sun Oct 10 14:34:20 CST 2023延迟3s执行的任务Sun Oct 10 14:34:25 CST 2023延迟3s执行的任务Sun Oct 10 14:34:30 CST 2023`Timer的实现方式比较简单,其内部有两个主要的属性:
`/** * 用于存放定时任务TimeTask的列表 */private final TaskQueue queue = new TaskQueue();/** * 用于执行定时任务的线程 */private final TimerThread thread = new TimerThread(queue);`TimerTask是一个实现了Runnable接口的抽象类。其run()方法用于提供具体的延时任务逻辑。
TaskQueue内部采用的是小顶堆的算法实现。根据任务的触发时间采用死循环的方式进行排序,将执行时间最小的任务放在前面。
`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 4) { throw new IllegalArgumentException("Invalid cron expression "" + this.expression + "" led to runaway search for next trigger"); } doNext(calendar, dot); }}private int findNext(BitSet bits, int value, Calendar calendar, int field, int nextField, List lowerOrders) { int nextValue = bits.nextSetBit(value); // 下一个匹配值是-1,则将对更高的域做加1操作,从0开始查找下一个匹配值,将当前域设置为下一个匹配值,重置比当前域低的所有域设置为最小值,递归调度本算法。 if (nextValue == -1) { calendar.add(nextField, 1); reset(calendar, Collections.singletonList(field)); nextValue = bits.nextSetBit(0); } // 下一个匹配值不是当前值但也不是-1,则将当前域设置为下一个匹配值,将比当前域低的所有域设置为最小值,递归调度本算法 if (nextValue != value) { calendar.set(field, nextValue); reset(calendar, lowerOrders); } // 下一个匹配值就是当前值,则匹配通过,如果当前域是月则算法结束,否则继续处理下一个更高的域。 return nextValue;}` Quartz在这里还是使用Spring Boot 集成Quartz;
引入依赖:
` org.quartz-scheduler quartz 2.3.0`测试的job业务处理类:
`/** * @className: QuartzJob * @description: 业务逻辑处理类 * @author: charon * @create: 2023-10-11 14:32 */public class QuartzJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("执行quartz定时器开始:" + new Date()); // 模拟业务逻辑 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("执行quartz定时器结束:" + new Date()); }}`实例化Job,将任务触发器加入任务调度中:
`/** * @className: QuartzConfig * @description: scheduler的启动、结束等控制类 * @author: charon * @create: 2023-10-11 14:38 */@Configurationpublic class QuartzConfig { @Autowired private Scheduler scheduler; /** * 开始定时器 */ public void startJob() throws SchedulerException { // 通过JobBuilder构建JobDetail实例,JobDetail规定只能是实现Job接口的实例 // JobDetail 是具体Job实例 JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity("job", "group").build(); // 基于表达式构建触发器 每5秒种执行一次 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?"); // CronTrigger表达式触发器 继承于Trigger // TriggerBuilder 用于构建触发器实例 CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("job1", "group1") .withSchedule(cronScheduleBuilder).build(); scheduler.scheduleJob(jobDetail, cronTrigger); } /** * 删除某个任务 * * @param name job的名称 * @param group job的分组 * @throws SchedulerException */ public void deleteJob(String name, String group) throws SchedulerException { JobKey jobKey = new JobKey(name, group); if (scheduler.checkExists(jobKey)){ scheduler.deleteJob(jobKey); } }`测试类(spring容器初始化完成后执行):
`/** * @className: QuartzTest * @description: 测试quartz的定时器 * @author: charon * @create: 2023-10-11 10:33 */@Configurationpublic class QuartzTest implements ApplicationListener { @Autowired private QuartzConfig quartzConfig; /** * 监听初始化quartz * @param event */ @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println("容器初始化完成"); try { quartzConfig.startJob(); } catch (SchedulerException e) { e.printStackTrace(); } }}容器初始化完成2023-10-11 15:02:46.947 INFO 19628 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 7010 (http) with context path '2023-10-11 15:02:49.558 INFO 19628 --- [ main] c.c.ScheduleApplication : Started ScheduleApplication in 10.734 seconds (JVM running for 11.644)执行quartz定时器开始:Mon Oct 11 15:02:50 CST 2023执行quartz定时器结束:Mon Oct 11 15:02:52 CST 2023执行quartz定时器开始:Mon Oct 11 15:02:55 CST 2023执行quartz定时器结束:Mon Oct 11 15:02:57 CST 2023执行quartz定时器开始:Mon Oct 11 15:03:00 CST 2023执行quartz定时器结束:Mon Oct 11 15:03:02 CST 2023`