目录
- 一、定时器
- 1.1 什么是定时器?
- 1.2 定时器的构成
- 二、简单实现定时器
一、定时器
1.1 什么是定时器?
定时器是多线程编码中的一个重要组件,它就好比一个闹钟,例如我们想去坐车,但是不想现在去坐车,想8:30去坐车,于是我们订了一个8点钟的闹钟,也就是说定时器有一些逻辑,但并不想立即执行,而是要等一定的时间之后再来执行.
使用场景:浏览器访问服务器的时候就会使用定时器来判定是否超时, 标准库中其实已经提供了阻塞队列,定时器等基本组件,实际工作中我们直接使用标准库的就可以,下面的代码主要是为了理解其原理,加深对多线程的理解.
1.2 定时器的构成
- 使用一个Task类来描述"一段逻辑",也可以说一个要执行的任务,同时也要记住这个任务在什么时候开始执行;
- 使用一个阻塞优先队列来组织若干个Task;
这个阻塞优先队列既支持阻塞的特性,又支持按优先级的"先进先出" ,使用优先级队列的目的是为了保证队首元素就是那个最早要被执行的任务,本质上就是一个堆.判断当前队列中是否有任务时间到了,只需要判断队首元素是否到时间即可.
定时器四部分:- Task类描述任务
- 阻塞优先队列组织任务
- 扫描线程,定时扫描队首元素
- 接口,让调用者能安排任务给定时器
标准库中的定时器
标准库中提供了一个 Timer 类, Timer 类的核心方法为schedule.
Timer类构造时内部会创建线程, 有下面的四个构造方法, 可以指定线程名和是否将定时器内部的线程指定为后台线程(即守护线程), 如果不指定, 定时器对象内部的线程默认为前台线程.
schedule 方法是给Timer注册一个任务, 这个任务在指定时间后进行执行, TimerTask类就是专门描述定时器任务的一个抽象类, 它实现了Runnable接口.
import java.util.Timer; import java.util.TimerTask; public class Test { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("执行延后3s的任务!"); } }, 3000); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("执行延后2s后的任务!"); } }, 2000); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("执行延后1s的任务!"); } }, 1000); } }
二、简单实现定时器
//自己实现的定时器类 class MyTimer { //扫描线程 private Thread t = null; //阻塞队列,存放任务 private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>(); public MyTimer() { //构造扫描线程 t = new Thread(() -> { while (true) { //取出队首元素,检查队首元素执行任务的时间 //时间没到,再把任务放回去 //时间到了,就执行任务 try { synchronized (this) { MyTask task = queue.take(); long curTime = System.currentTimeMillis(); if (curTime < task.getTime()) { //时间没到,放回去 queue.put(task); //放回任务后,不应该立即就再次取出该任务 //所以wait设置一个阻塞等待,以便新任务到时间或者新任务来时后再取出来 this.wait(task.getTime() - curTime); } else { //时间到了,执行任务 task.run(); } } } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); } /** * 注册任务的方法 * @param runnable 任务内容 * @param after 表示在多少毫秒之后执行. 形如 1000 */ public void schedule (Runnable runnable, long after) { //获取当前时间的时间戳再加上任务时间 MyTask task = new MyTask(runnable, System.currentTimeMillis() + after); queue.put(task); //每次当新任务加载到阻塞队列时,需要中途唤醒线程,因为新进来的任务可能是最早需要执行的 synchronized (this) { this.notify(); } } } public class Test { public static void main(String[] args) { MyTimer timer = new MyTimer(); timer.schedule(new Runnable() { @Override public void run() { System.out.println("2s后执行的任务1"); } }, 2000); timer.schedule(new Runnable() { @Override public void run() { System.out.println("2s后执行的任务1"); } }, 1000); } }