Java多线程案例【定时器】
- 🍒一.什么是定时器
- 🍒二.标准库中的定时器(timer)
- 🍎2.1什么是定时器
- 🍎2.2定时器的使用
- 🍒三.实现定时器
- 🍎3.1什么是定时器
- 🍎3.2最终实现代码
🍒一.什么是定时器
定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定好的代码
定时器是一种实际开发中非常常用的组件,我们举几个例子:
1.比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连
2.比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除)
以上类似于这样的场景就需要用到定时器
🍒二.标准库中的定时器(timer)
🍎2.1什么是定时器
标准库中供了一个 Timer 类. Timer 类的核心方法为 schedule ,schedule 包含两个参数. 第一个参数指定即将要执行的任务代码TimerTask, 第二个参数指定多长时间之后执行 (单位为毫秒).
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
}, 3000);
🍎2.2定时器的使用
Timer的构造方法
构造方法 | 说明 |
public Timer() | 无参数构造方法,默认定时器关联的线程不是守护线程,线程名字也是默认值 |
public Timer(boolean isDaemon) | 指定定时器中关联的线程是否为守护线程,如果是,参数为true |
public Timer(String name) | 指定定时器关联线程名称,线程类型默认为非守护线程 |
public Timer(String name, boolean isDaemon) | 指定定时器关联线程名和线程类型 |
Timer方法
方法 | 说明 |
public void schedule (TimerTask task, long delay) | 指定任务,延迟多久执行该任务 |
public void schedule(TimerTask task, Date time) | 指定任务,指定任务的执行时间 |
public void schedule(TimerTask task, long delay, long period) | 连续执行指定任务,延迟时间,连续执行任务的时间间隔,毫秒为单位 |
public void schedule(TimerTask task, Date firstTime, long period) | 连续执行指定任务,第一次任务的执行时间,连续执行任务的时间间隔 |
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 连续执行指定任务,第一次任务的执行时间,连续执行任务的时间间隔 |
public void scheduleAtFixedRate(TimerTask task, long delay, long period) | 连续执行指定任务,延迟时间,连续执行任务的时间间隔,毫秒为单位 |
public void cancel() | 终止定时器所有任务,终止执行的任务不受影响 |
TimerTask是专门来实现Runnable接口的
下面我们会实现一下定时器,我们就不用TimerTask了,我们直接使用Runnable,因为TimerTask实现了Runnable接口,所以后面测试我们自己所写的schedule方法时,也可以传入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("执行线程在5s后执行");
}
},5000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行线程在2s后执行");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行线程在3s后执行");
}
},3000);
}
}
🍒三.实现定时器
🍎3.1什么是定时器
定时器的构成:一个带优先级的阻塞队列
为啥要带优先级呢?
因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.
1.队列中的每个元素是一个 Task 对象,Task 中带有一个时间属性, 队首元素就是即将同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
class MyTask implements Comparable<MyTask>{
//执行的时间戳
private long time;
//接受具体任务
private Runnable runnable;
//创建MyTask构造方法
public MyTask(Runnable runnable,long time) {
//通过currentTimeMillis来获取time 中存的是绝对时间, 超过这个时间的任务就应该被执行
this.time = System.currentTimeMillis()+time;
this.runnable = runnable;
}
//执行任务
public void run(){
this.runnable.run();
}
//提供对外time
public long getTime() {
return time;
}
//执行comparable接口来进行时间的比较,并将time的long类型转换为int类型
@Override
public int compareTo(MyTask o) {
return (int)(this.time-o.time);
}
}
- Timer 实例中, 通过 PriorityBlockingQueue 来组织若干个 Task 对象.通过 schedule 来往队列中插入一个个 Task 对象.
class MyTimer{
// 定时器内部要能够存放多个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
//为锁创建一个对象
Object locker = new Object();
public void schedule(Runnable runnable, long delay) {
MyTask task = new MyTask(runnable, delay);
queue.put(task);
// 每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行~~
synchronized (locker) {
locker.notify();
}
}
- Timer 类中存在一个 worker 线程, 一直不停的扫描队首元素, 看看是否能执行这个任务.所谓 “能执行” 指的是该任务设定的时间已经到达了
class MyTimer{
// 定时器内部要能够存放多个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
//为锁创建一个对象
Object locker = new Object();
public void schedule(Runnable runnable, long delay) {
MyTask task = new MyTask(runnable, delay);
queue.put(task);
// 每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行~~
synchronized (locker) {
locker.notify();
}
}
public MyTimer() {
Thread t = new Thread(() -> {
while (true) {
try {
// 先取出队首元素
MyTask task = queue.take();
// 再比较一下看看当前这个任务时间到了没?
long curTime = System.currentTimeMillis();
if (curTime < task.getTime()) {
// 时间没到, 把任务再塞回到队列中.
queue.put(task);
// 指定一个等待时间,防止有的线程需要等待时间很长,但是线程一直运行等待时间到来执行,这样会占有CPU占有资源
synchronized (locker) {
locker.wait(task.getTime() - curTime);
}
} else {
// 时间到了, 执行这个任务
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
🍎3.2最终实现代码
package thread;
import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;
// 创建一个类, 表示一个任务.
class MyTask implements Comparable<MyTask> {
// 任务具体要干啥
private Runnable runnable;
// 任务具体啥时候干. 保存任务要执行的毫秒级时间戳
private long time;
// after 是一个时间间隔. 不是绝对的时间戳的值
public MyTask(Runnable runnable, long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run() {
runnable.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTask o) {
// 到底是谁见谁, 才是一个时间小的在前? 需要咱们背下来.
return (int) (this.time - o.time);
}
}
class MyTimer {
// 定时器内部要能够存放多个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
public void schedule(Runnable runnable, long delay) {
MyTask task = new MyTask(runnable, delay);
queue.put(task);
// 每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行~~
synchronized (locker) {
locker.notify();
}
}
private Object locker = new Object();
public MyTimer() {
Thread t = new Thread(() -> {
while (true) {
try {
// 先取出队首元素
MyTask task = queue.take();
// 再比较一下看看当前这个任务时间到了没?
long curTime = System.currentTimeMillis();
if (curTime < task.getTime()) {
// 时间没到, 把任务再塞回到队列中.
queue.put(task);
// 指定一个等待时间
synchronized (locker) {
locker.wait(task.getTime() - curTime);
}
} else {
// 时间到了, 执行这个任务
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public class Test {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello timer!");
}
}, 3000);
System.out.println("main");
}
}