文章目录
什么是定时器?
定时器的功能和"闹钟"类似,代码中的定时器通常都是"多长时间之后,执行某个动作"。
定时器的实现
标准库中的定时器
java
// 标准库的定时器 执行完任务之后线程不会退出 需要手动杀死线程
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行第一次定时器任务");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行第二次定时器任务");
}
},4000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行第三次定时器任务");
}
},5000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行第四次定时器任务");
}
},6000);
System.out.println("定时器启动");
}
注:
- 一个定时器可以安排多个任务。
- 调用schedule安排任务时,一定要重写run方法,明确任务内容
- 定时器开启后不会自动结束,得手动杀死进程
自己实现一个定时器
版本一:实现简单的定时器
- schedule方法是用来描述任务内容的。第一个参数是:任务内容;第二个参数是:任务多长时间后执行
java
public MyTask(Runnable runnable, long delay){
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void schedule(Runnable runnable,long delay){
MyTask myTask = new MyTask(runnable, delay);
}
-
用什么数据结构来存储任务呢?
我们首先要考虑的是要让任务设置时间最短的先执行,这就有个优先级的问题,所以使用优先级队列
同时,我们通常都会使用多个线程来执行这些设置的任务,就要保证线程安全。
综上,我们最后决定使用优先级阻塞队列来存储任务
-
我们创建一个专门的线程来执行定时器里的任务
java
//最基础的定时器完成
class MyTimer{
private PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();
public MyTimer(){
Thread thread = new Thread(() -> {
while (true){
try {
MyTask myTask = priorityBlockingQueue.take();
if (System.currentTimeMillis() >= myTask.getTime()){
myTask.getRunnable().run();
}else {
priorityBlockingQueue.put(myTask);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
private void schedule(Runnable runnable,long delay){
MyTask myTask = new MyTask(runnable,delay);
priorityBlockingQueue.put(myTask);
}
}
版本二:解决thread线程的忙等问题
- 当定时器启动后有一个问题:不管任务时间有多久,thread线程一直执行,会占用CPU资源。我们想个办法让它在任务时间还没到的时候停下来,就要使用wait-notify
java
class MyTimer{
private PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();
private Object locker = new Object();
public MyTimer(){
Thread thread = new Thread(() -> {
while (true){
try {
MyTask myTask = priorityBlockingQueue.take();
if (System.currentTimeMillis() >= myTask.getTime()){
myTask.getRunnable().run();
}else {
priorityBlockingQueue.put(myTask);
synchronized (locker){
locker.wait(myTask.getTime() - System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
private void schedule(Runnable runnable,long delay){
MyTask myTask = new MyTask(runnable,delay);
priorityBlockingQueue.put(myTask);
}
}
版本三:解决漏掉任务的问题
- 又一个问题:如果schedule线程中新添加了一个任务 执行时间比wait时间短 不就错过了嘛。使用notify
java
class MyTimer{
private PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();
private Object locker = new Object();
public MyTimer(){
Thread thread = new Thread(() -> {
while (true){
try {
MyTask myTask = priorityBlockingQueue.take();
if (System.currentTimeMillis() >= myTask.getTime()){
myTask.getRunnable().run();
}else {
priorityBlockingQueue.put(myTask);
synchronized (locker){
locker.wait(myTask.getTime() - System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
private void schedule(Runnable runnable,long delay){
MyTask myTask = new MyTask(runnable,delay);
priorityBlockingQueue.put(myTask);
synchronized (locker){
locker.notify();
}
}
}
版本四:解决notify先执行的问题
- 如果notify先执行了 take在notify之前执行 也就是说放进去了新的元素 但是take这边的线程没感觉到不就还没起到效果嘛。 扩大锁的范围
java
class MyTimer{
private PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();
private Object locker = new Object();
public MyTimer(){
Thread thread = new Thread(() -> {
while (true){
try {
synchronized (locker) {
MyTask myTask = priorityBlockingQueue.take();
if (System.currentTimeMillis() >= myTask.getTime()) {
myTask.getRunnable().run();
} else {
priorityBlockingQueue.put(myTask);
locker.wait(myTask.getTime() - System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
public void schedule(Runnable runnable,long delay){
MyTask myTask = new MyTask(runnable, delay);
priorityBlockingQueue.put(myTask);
//这里起到的作用就是 刷新一下 看看队列中有没有执行时间更早的任务 有两种情况
//1. 先放进去再刷新查看 先放进去最开始没注意到也没事儿 刷新的时候就会注意到重新计算wait时间
//2. 先查看再放进去 会死锁(把notify的锁范围放大) 在一开始队列里没有数据的时候 会等待添加元素 但添加元素得获取到锁 此时阻塞的时候已经锁上了take
// 没有人释放锁 所以 死锁了
synchronized (locker) {
locker.notify();
}
}
}