多线程当中的定时器
本篇文章 , 给大家带来的是多线程当中的定时器 , 定时器是一个线程工具,可以用于调度多个定时任务以后台线程的方式运行。
比如说,在开发中,我们需要做一些周期性的操作,比如每隔三分钟就清空一次文件夹,用定闹钟的方式人为的清空肯定是不合适的。这时就可以通过 Timer 和 TimerTask 类来实现,定时完成具体任务的功能。
大家可以跳转到此页面观看效果更加
上一篇文章的链接也给大家贴在这里了
定时器
1. 官方的定时器
import java.util.Timer;
import java.util.TimerTask;
public class Demo22 {
public static void main(String[] args) {
// java.util 里面的一个组件
Timer timer = new Timer();
// schedule 这个方法的效果是:"安排一个任务"
// 第一个参数:new TimerTask() + 重写的 run , 表示任务
// 第二个参数:多少毫秒之后开始执行任务
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("这是一个要执行的任务");
}
},3000);
}
}
那这是怎么回事呢 ?
实现定时器 , 背后涉及到多线程 !
Timer 里面有线程 , 这个线程的运行阻止了进程的退出
那为什么不使用 sleep 呢 ?
使用 sleep 是把当前线程给阻塞了 .
sleep 的时间里 , 你啥都干不了 , 就只能干等
但是使用定时器 , 之前的线程该干啥干啥
import java.util.Timer;
import java.util.TimerTask;
public class Demo22 {
public static void main(String[] args) throws InterruptedException {
// java.util 里面的一个组件
Timer timer = new Timer();
// schedule 这个方法的效果是:"安排一个任务"
// 第一个参数:new TimerTask() + 重写的 run , 表示任务
// 第二个参数:多少毫秒之后开始运行
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("这是一个要执行的任务");
}
},3000);
while(true) {
System.out.println("其他线程该干啥干啥");
Thread.sleep(1000);
}
}
}
2. 自己实现的计时器
Timer 其实可以往里面加入很多任务的
我们要实现的计时器 , 就需要考虑下面几个因素
- Timer内部要组织很多的任务
- Timer里的每个任务都要通过一定的方式来描述出来 (自己定义一个 TimerTask 类)
- 还需要有一个线程 , 通过这个线程来扫描定时器内部的任务 , 执行其中时间到了的任务
虽然这里的任务有很多 , 但是他们的执行顺序是一定的 , 按照时间顺序先后来执行 , 所以我们可以使用优先级队列来组织这些任务 , 时间小的 (time 属性小的) 先执行
但是使用优先级队列还有可能出现问题 :
这个队列会被多个线程同时访问
- schedule 可能是在多线程环境中被调用 , 每次调用都要往队列中添加元素
- 内部还需要有专门的线程来执行队列里的任务
读和写同步进行 , 就可能会造成线程不安全问题
所以我们选择使用 PriorityBlockingQueue
他既满足了优先级队列的特点 : 先进先出 , 又满足了阻塞队列的特点 : 线程安全
2.1 使用优先级阻塞队列
import java.util.concurrent.PriorityBlockingQueue;
// 描述任务的类
class MyTask {
// 任务要干啥
private Runnable command;
// 任务什么时候执行
private long time;
public MyTask(Runnable command,long after) {
this.command = command;
// 任务中需要记录一个完整的时间
// 记录到底什么时候(几分几秒)执行
// 比如 2023-01-30 09:24
this.time = System.currentTimeMillis() + after;
}
// 执行任务的方法,直接在内部调用 command 的 run 方法
public void run() {
command.run();
}
}
// 自己创建的定时器类
class MyTimer {
// 使用优先级队列来保存若干个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
// command 代表要执行的任务
// after 代表多长时间之后来执行这个任务
public void schedule(Runnable command,long after) {
// 创建任务
MyTask myTask = new MyTask(command, after);
// 把任务添加到优先级阻塞队列中
queue.put(myTask);
}
}
public class Demo23 {
}
接下来 , 我们就需要安排线程来执行队列中的任务 . 尤其是每次出队列 , 就要执行时间最短的任务
import java.util.concurrent.PriorityBlockingQueue;
// 描述任务的类
class MyTask {
// 任务要干啥
private Runnable command;
// 任务什么时候执行
// 设置成 public 方便下面访问
// 提供他的 public 的 get 方法也可以
public long time;
public MyTask(Runnable command,long after) {
this.command = command;
// 任务中需要记录一个完整的时间
// 记录到底什么时候(几分几秒)执行
// 比如 2023-01-30 09:24
this.time = System.currentTimeMillis() + after;
}
// 执行任务的方法,直接在内部调用 command 的 run 方法
public void run() {
command.run();
}
}
// 自己创建的定时器类
class MyTimer {
// 使用优先级队列来保存若干个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
// command 代表要执行的任务
// after 代表多长时间之后来执行这个任务
public void schedule(Runnable command,long after) {
// 创建任务
MyTask myTask = new MyTask(command, after);
// 把任务添加到优先级阻塞队列中
queue.put(myTask);
}
public MyTimer() {
// 在这里启动一个线程
Thread t = new Thread(() -> {
while(true) {
// 循环过程中,不停地从队列中获取到队首元素
// 我们首先需要判定队首元素当前时间是否就绪;就绪->执行,不就绪->不执行
// 比如这个任务是 12:30 的 , 现在是 12:15 , 就先不执行
try {
// 把任务取出来
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if(curTime < myTask.time) {
// 时间还未到
// 再把它放回去
queue.put(myTask);
} else {
// 时间到了
// 执行任务
myTask.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
public class Demo23 {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务1");
}
},2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务2");
}
},4000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务3");
}
},6000);
}
}
我们运行一下
这是因为我们没定义优先级的比较规则
2.2 定义比较规则
import java.util.concurrent.PriorityBlockingQueue;
// 描述任务的类
class MyTask implements Comparable<MyTask> {
// 任务要干啥
private Runnable command;
// 任务什么时候执行
// 设置成 public 方便下面访问
// 提供他的 public 的 get 方法也可以
private long time;
public MyTask(Runnable command,long after) {
this.command = command;
// 任务中需要记录一个完整的时间
// 记录到底什么时候(几分几秒)执行
// 比如 2023-01-30 09:24
this.time = System.currentTimeMillis() + after;
}
// 执行任务的方法,直接在内部调用 command 的 run 方法
public void run() {
command.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTask o) {
// 希望时间小的在前面
// 试一下就知道了
// this.time - o.time 得到的是 long,返回值是int
// 所以需要强转
return (int) (this.time - o.time);
}
}
// 自己创建的定时器类
class MyTimer {
// 使用优先级队列来保存若干个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
// command 代表要执行的任务
// after 代表多长时间之后来执行这个任务
public void schedule(Runnable command,long after) {
// 创建任务
MyTask myTask = new MyTask(command, after);
// 把任务添加到优先级阻塞队列中
queue.put(myTask);
}
public MyTimer() {
// 在这里启动一个线程
Thread t = new Thread(() -> {
while(true) {
// 循环过程中,不停地从队列中获取到队首元素
// 我们首先需要判定队首元素当前时间是否就绪;就绪->执行,不就绪->不执行
// 比如这个任务是 12:30 的 , 现在是 12:15 , 就先不执行
try {
// 把任务取出来
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if(curTime < myTask.getTime()) {
// 时间还未到
// 再把它放回去
queue.put(myTask);
} else {
// 时间到了
// 执行任务
myTask.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public class Demo23 {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务2");
}
},4000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务1");
}
},2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务3");
}
},6000);
}
}
但是我们的代码 , 还存在可以优化的地方
2.3 解决 CPU 空转 : wait
import java.util.concurrent.PriorityBlockingQueue;
// 描述任务的类
class MyTask implements Comparable<MyTask> {
// 任务要干啥
private Runnable command;
// 任务什么时候执行
// 设置成 public 方便下面访问
// 提供他的 public 的 get 方法也可以
private long time;
public MyTask(Runnable command, long after) {
this.command = command;
// 任务中需要记录一个完整的时间
// 记录到底什么时候(几分几秒)执行
// 比如 2023-01-30 09:24
this.time = System.currentTimeMillis() + after;
}
// 执行任务的方法,直接在内部调用 command 的 run 方法
public void run() {
command.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTask o) {
// 希望时间小的在前面
// 试一下就知道了
// this.time - o.time 得到的是 long,返回值是int
// 所以需要强转
return (int) (this.time - o.time);
}
}
// 自己创建的定时器类
class MyTimer {
// 这个是用来阻塞等待的锁对象
// 方便下面使用 wait
private Object locker = new Object();
// 使用优先级队列来保存若干个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
// command 代表要执行的任务
// after 代表多长时间之后来执行这个任务
public void schedule(Runnable command, long after) {
// 创建任务
MyTask myTask = new MyTask(command, after);
// 把任务添加到优先级阻塞队列中
queue.put(myTask);
synchronized (locker) {
locker.notify();
}
}
public MyTimer() {
// 在这里启动一个线程
Thread t = new Thread(() -> {
while (true) {
// 循环过程中,不停地从队列中获取到队首元素
// 我们首先需要判定队首元素当前时间是否就绪;就绪->执行,不就绪->不执行
// 比如这个任务是 12:30 的 , 现在是 12:15 , 就先不执行
try {
// 把任务取出来
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if (curTime < myTask.getTime()) {
// 时间还未到
// 再把它放回去
queue.put(myTask);
synchronized (locker) {
// myTask.getTime() - curTime 任务时间-当前时间=等待时间
locker.wait(myTask.getTime() - curTime);
}
} else {
// 时间到了
// 执行任务
myTask.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public class Demo23 {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务2");
}
}, 4000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务1");
}
}, 2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务3");
}
}, 6000);
}
}
但是 , 当前代码还是有点问题 …
2.4 防止其他线程插入到入队列和线程等待之间
出现问题的位置是 put 和 synchronized 之间很有可能被别的线程的任务插入
所以我们把 synchronized 范围扩大 , 把 put 和 wait 打包成一组操作
import java.util.concurrent.PriorityBlockingQueue;
// 描述任务的类
class MyTask implements Comparable<MyTask> {
// 任务要干啥
private Runnable command;
// 任务什么时候执行
// 设置成 public 方便下面访问
// 提供他的 public 的 get 方法也可以
private long time;
public MyTask(Runnable command, long after) {
this.command = command;
// 任务中需要记录一个完整的时间
// 记录到底什么时候(几分几秒)执行
// 比如 2023-01-30 09:24
this.time = System.currentTimeMillis() + after;
}
// 执行任务的方法,直接在内部调用 command 的 run 方法
public void run() {
command.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTask o) {
// 希望时间小的在前面
// 试一下就知道了
// this.time - o.time 得到的是 long,返回值是int
// 所以需要强转
return (int) (this.time - o.time);
}
}
// 自己创建的定时器类
class MyTimer {
// 这个是用来阻塞等待的锁对象
// 方便下面使用 wait
private Object locker = new Object();
// 使用优先级队列来保存若干个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
// command 代表要执行的任务
// after 代表多长时间之后来执行这个任务
public void schedule(Runnable command, long after) {
// 创建任务
MyTask myTask = new MyTask(command, after);
synchronized (locker) {
// 把任务添加到优先级阻塞队列中
queue.put(myTask);
locker.notify();
}
}
public MyTimer() {
// 在这里启动一个线程
Thread t = new Thread(() -> {
while (true) {
// 循环过程中,不停地从队列中获取到队首元素
// 我们首先需要判定队首元素当前时间是否就绪;就绪->执行,不就绪->不执行
// 比如这个任务是 12:30 的 , 现在是 12:15 , 就先不执行
try {
synchronized (locker) {
// 把任务取出来
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if (curTime < myTask.getTime()) {
// 时间还未到
// 再把它放回去
queue.put(myTask);
synchronized (locker) {
// myTask.getTime() - curTime 任务时间-当前时间=等待时间
locker.wait(myTask.getTime() - curTime);
}
} else {
// 时间到了
// 执行任务
myTask.run();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public class Demo23 {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务2");
}
}, 4000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务1");
}
}, 2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务3");
}
}, 6000);
}
}
2.5 队列初识为空的情况下 , 需要阻塞等待
import java.util.concurrent.PriorityBlockingQueue;
// 描述任务的类
class MyTask implements Comparable<MyTask> {
// 任务要干啥
private Runnable command;
// 任务什么时候执行
// 设置成 public 方便下面访问
// 提供他的 public 的 get 方法也可以
private long time;
public MyTask(Runnable command, long after) {
this.command = command;
// 任务中需要记录一个完整的时间
// 记录到底什么时候(几分几秒)执行
// 比如 2023-01-30 09:24
this.time = System.currentTimeMillis() + after;
}
// 执行任务的方法,直接在内部调用 command 的 run 方法
public void run() {
command.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTask o) {
// 希望时间小的在前面
// 试一下就知道了
// this.time - o.time 得到的是 long,返回值是int
// 所以需要强转
return (int) (this.time - o.time);
}
}
// 自己创建的定时器类
class MyTimer {
// 这个是用来阻塞等待的锁对象
// 方便下面使用 wait
private Object locker = new Object();
// 使用优先级队列来保存若干个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
// command 代表要执行的任务
// after 代表多长时间之后来执行这个任务
public void schedule(Runnable command, long after) {
// 创建任务
MyTask myTask = new MyTask(command, after);
synchronized (locker) {
// 把任务添加到优先级阻塞队列中
queue.put(myTask);
locker.notify();
}
}
public MyTimer() {
// 在这里启动一个线程
Thread t = new Thread(() -> {
while (true) {
// 循环过程中,不停地从队列中获取到队首元素
// 我们首先需要判定队首元素当前时间是否就绪;就绪->执行,不就绪->不执行
// 比如这个任务是 12:30 的 , 现在是 12:15 , 就先不执行
try {
synchronized (locker) {
while (queue.isEmpty()) {
locker.wait();
}
// 把任务取出来
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if (curTime < myTask.getTime()) {
// 时间还未到
// 再把它放回去
queue.put(myTask);
synchronized (locker) {
// myTask.getTime() - curTime 任务时间-当前时间=等待时间
locker.wait(myTask.getTime() - curTime);
}
} else {
// 时间到了
// 执行任务
myTask.run();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public class Demo23 {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务2");
}
}, 4000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务1");
}
}, 2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务3");
}
}, 6000);
}
}
到此为止 , 多线程当中的定时器就给大家讲解完毕了
如果对你有收获的话 , 请一键三连~