在前面的一张我们以及聊了我们为什么要使用线程,线程的好处,以及线程怎么使用,最后就是我们的重点线程安全,兄弟们可以去复习一下。因为这篇我们就是有了前面的基础再实现定时器的。(完整的代码都在本章最下面)
定时器
作用
那废话不多说,现在开始聊我们的定时器,我们先想想定时器有什么作用,先知道有什么作用了之后才可以理解并且写出来吧,普通的定时器就是到点就响,当然这个没有什么写的必要,和多线程碰不到边,所以我们现在要聊的是,定时器pro,我们给计算机一个任务,再告诉计算机什么时候执行,让计算机到点了就自己执行,当然一个有点少,所以我们可以创建多个任务让计算机,自己根据时间来执行相关的任务
实现
既然我们要给计算机多个任务,所以我们就需要使用一个数据结构来存储这些数据,那我们要使用什么数据结构呢,就需要看我们需要的数据结构具体要实现什么功能,那你想想我们想要定时器存储的数据实现什么呢,对,就是根据时间的先后输出任务就好了,所以,我们的数据结构就想要一个可以输出时间最早的任务,而要最早时间的数据结构,有那个符合啊,想到最,我们就应该可以想到堆(优先级队列),所以我们就要先创建一个优先级队列,来存储我们就任务和任务的执行时间,但是我们的优先级队列只能存储一个类型啊,不可以同时存储任务和任务的时间啊,所以我们就需要把任务和任务时间包装成一个类不就好了吗。并且写上构造方法

但是你想想假如领导叫我们半个小时之后去帮他拿快递,我们就这样记下来,半小时后拿快递,但是现在的半个小时,具体是什么时候(现在的半个小时,过5分钟就不是半个小时了),所以这样的时间计数就是不清楚的所以我们会写(假如现在2点半)3点帮领导拿快递,就以绝对时间来记录。所以我们的代码就改为
我们在写上Get和Set方法就好了。我们就把一个任务处理好了,就开始写定时器的主体了。
上面说了需要一个堆(优先级队列)这样的数据结构来存储数据,所以我们就先创建

然后我们就需要一个线程来实现定时器的运用逻辑,所以我们先创建一个线程
想想,定时器的主体逻辑怎么实现,是不是需要一个循环(这个循环要一直进行,因为我们随时会向堆里面加任务),在这个堆上面循环取出任务时间最早的任务,开始执行就好了,那就有两中可能,如果这个堆里面是空,我们怎么办,就continue就好了(后续我们会有优化手段),一种可能就是堆不为空了,那我们就需要判断一下这个任务的执行时间到了没有(那为什么我们不需要判断其他的任务执行时间好了没有啊,因为我们这个是优先级队列,而且是以时间来比较,所以我们现在就可以来写比较方法了)
而这个什么时候用this - o ,什么时候用o - this,你不需要理解,你自己写的时候,都试一试是性价比最高的。好,还是上文聊的判断这个任务的执行时间到了没有,如果到了,我们就把这个任务跑起来就好了,对了,还要把这个数据从堆里面移除哦
另一种情况就是任务的执行时间还没有到,就和上面一样continue咯,最后就是线程开始就好了。
所以我们就差最后一步了,就是把任务添加到堆(优先级队列)中就好了,这个比较简单,就是
我们的定时器就好了
我们先看看全部代码,这个是参考,后面还有改进的版本
import java.util.Comparator;
import java.util.PriorityQueue;
public class MainB {
public static void main(String[] args){
MyTime myTime = new MyTime();
for(int i = 0;i<10;i++){
int j = i;
myTime.Schedule(new Runnable(){
@Override
public void run(){
System.out.println("定时任务 100 "+j);
}
},100);
myTime.Schedule(new Runnable(){
@Override
public void run(){
System.out.println("定时任务 500 "+j);
}
},500);
}
}
}
class MyTask implements Comparable<MyTask>{
@Override
public int compareTo(MyTask o){
return (int) (this.GetTime() - o.GetTime());
}
private Runnable runnable;
private long time;
MyTask(Runnable runnable,long time){
this.runnable = runnable;
this.time = time + System.currentTimeMillis();
}
public Runnable GetRunnable(){
return runnable;
}
public long GetTime(){
return time;
}
}
class MyTime {
public void Schedule(Runnable runnable,long time){
MyTask myTask1 = new MyTask(runnable,time);
queue.offer(myTask1);
}
private PriorityQueue<MyTask> queue = new PriorityQueue<>();
public MyTime(){
Thread thread = new Thread(()->{
while (true){
if(queue.isEmpty()){
continue;
}
MyTask myTask = queue.peek();
long NowTime = System.currentTimeMillis();
if(myTask.GetTime() > NowTime){
myTask.GetRunnable().run();
queue.poll();
}else {
continue;
}
}
});
thread.start();
}
}
线程安全
你看上面的代码,你觉得有没有问题(问了就肯定有),对,那你可以想到哪里有问题不。
如果你连线程安全有哪些都想不起来,就把这篇翻到最上面,那里有一个链接点进去。
线程主要有两种(可重入不算因为我们使用的锁synchronized就是可重入锁)一是,线程的随机调度和可见性问题,因为我们这里没有触发可见性的变量,所以我可以告诉你,这里的线程问题就是随机的调度问题,这样你在来想想这个代码有那里要改,线程随机调度说到底就是,我们在修改一个变量的时候,不希望同时有操作也在修改,所以我们在这里避免一下,加任务和读出任务就好了,所以我们在Schedule和MyTime中加synchronized就好了,但是这个其实还有一点需要优化的地方,前面埋了铺垫哦。就是continue的地方,现在我们在这个地方加了锁,我们还一直continue,是不是就是有点占着茅坑不拉屎感觉,我拿着锁,就是不用,是不是,就都导致后面我要Schedule使用不了,所以说这里我们需要优化,但是怎么优化呢,特别简单,就是使用wait(这个就是休眠一直到计算机使用notify来唤醒他,而我们有两个continue的地方,第一个在
我们判断这个堆(优先级队列)是不是空的时候,这里直接变为空就好了。我们就在Schedule使用的时候,加一个notify就可以了。
第二个在
我们要执行的任务时间还没有到,所以我们要wait,但是我们在加任务的时候会notify会不会把这个唤醒了啊,当然是会的,但是你想这个程序醒来了,我继续向下走就是while(true)又回到开头了(假如新任务时间是最早的,刚刚好就判断新任务时间到了没有,)老任务不过是走到执行时间没有到的位置继续wait,但是我这里wait就没有计算机唤醒我了,所以我需要直接醒来,我们就可以给wait设置一个睡眠最大时间就是要唤醒的时间减当前时间,就好了,定时器就写好了。
我们就来聊聊,一个小问题,我给wait设置一个睡眠的最大时间,而不唤醒,不就是sleep吗。我们从使用上面来看其实是这样的,但是我们使用wait的初衷就是为了去唤醒他,我们设置一个最大值只是因为防范于未来(就是一直不唤醒,从而带来的影响)。
总结
定时器虽然代码不多,但是每一个地方都是需要考虑的,所以写一个定时器可以对自己的多线程相当于一个实践一样,同时看看这个多线程有没有那里没有学懂。下面就是全部的代码了
import java.util.PriorityQueue;
class MyTimerTask implements Comparable<MyTimerTask>{//这个是定时器的一个任务
@Override
public int compareTo(MyTimerTask o){
return(int)(this.delay - o.delay);
}
private Runnable task;
private long delay;
public MyTimerTask(Runnable task , long delay){
this.delay = System.currentTimeMillis() + delay;
this.task = task;
}
public Runnable GetTask(){
return task;
}
public long GetTime(){
return delay;
}
}
class MyTimer{//这个是定时器的主体
private Object locker = new Object();
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
public MyTimer(){
Thread t = new Thread(() -> {
while (true) {
synchronized (locker) {
try {
if (queue.isEmpty()) {
continue;
}//下面就是不为空
MyTimerTask task = queue.peek();
long NowTime = System.currentTimeMillis();
if (NowTime < task.GetTime()) {
//不执行
locker.wait(task.GetTime() - NowTime);
} else {
//执行
task.GetTask().run();
queue.poll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
}
public void schedule(Runnable task, long delay){
synchronized (locker){
locker.notify();
MyTimerTask myTimerTask = new MyTimerTask(task,delay);
queue.offer(myTimerTask);
}
}
}
public class Main {
public static void main(String[] args){
MyTimer timer = new MyTimer();
for(int i = 0;i<10;i++){
int j = i;
timer.schedule(new Runnable(){
@Override
public void run(){
System.out.println("定时任务 500 "+j);
}
},500);
timer.schedule(new Runnable(){
@Override
public void run(){
System.out.println("定时任务 100 "+j);
}
},100);
}
}
}
1万+



被折叠的 条评论
为什么被折叠?



