多线程系列文章目录
💥 💥 💥如果你觉得我的文章有帮助到你,还请【关注➕点赞➕收藏】,得到你们支持就是我最大的动力!!!
💥 💥 💥
⚡版权声明:本文由【马上回来了】原创、在CSDN首发、需要转载请联系博主。
版权声明:本文为CSDN博主「马上回来了」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
🚀🚀🚀 新的知识开始喽🚀🚀🚀
1.标准库里的定时器
标准库里提供了Timer类,Timer的核心方法是schedule,schedule有两个参数,第一个参数是指定将要执行的任务代码,第二个参数是指定多长时间后执行,单位为毫秒.
public class Time {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {//TimerTask就是一个Runnable接口,重写run方法,改线程要执行的任务
@Override
public void run() {
System.out.println("时间到了!!!快起床!!!");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("1");
}
},4000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("2");
}
},5000);
System.out.println("开始计时");
}
}
2.定时器的模拟实现
定时器的构成:
1.带优先级的阻塞队列:时间靠前的先执行
2.队列里的每个元素都是一个Task对象
3.Task对象包括要执行的任务和执行的时间
4.同时有个worker线程一直扫描队首元素,看队首元素是否要执行
完整代码:
class Task implements Comparable<Task>{//Task得放入到带有优先级的阻塞队列里,因此必须可比较的,实现Comparable接口
//得有两个成员变量 1.指定执行的任务 2.delay执行的时间 转化为时间戳
Runnable runnable;
long time;
public Task(Runnable runnable,long delay){
this.runnable = runnable;
this.time = System.currentTimeMillis()+delay;
}
@Override
public int compareTo(Task o) {
return (int)(time - o.time);
}
}
class MyTMR{
BlockingQueue<Task> queue = new PriorityBlockingQueue<>();
public void schedule(Runnable runnable,long delay) throws InterruptedException {
//当添加一个任务时,就得创建一个Task对象,并且将task放入到优先级阻塞队里里
Task task = new Task(runnable,delay);
queue.put(task);
synchronized (this){
notify();//唤醒else里的wait
}
}
//在优先级阻塞队列里有了task之后,worker扫描线程就可以持续扫描优先级阻塞队列了
public MyTMR(){
Thread t = new Thread(){
@Override
public void run() {
synchronized (this){
while(true){
try {
//取出队首元素
Task task = queue.take();
//判断队首元素的任务是否要执行
/*
当前时间大于或等于最初设定的时间就得执行任务了
*/
if(System.currentTimeMillis()>=task.time){
task.runnable.run();
}else{//还未到达时间,将队首元素放回到队里,进行等待
queue.put(task);
//等待的时间:
wait(task.time - System.currentTimeMillis());
//如果有队列里有新的任务,queue.put后会唤醒wait,重新扫描优先级阻塞队列里的队首元素,如果有更早要执行的任务,那么等待的时间就会更新
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
t.start();
}
}
public class demo0 {
public static void main(String[] args) throws InterruptedException {
MyTMR mytimer = new MyTMR();
mytimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("这是任务1");
}
},3000);//3秒后执行
mytimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("这是任务2");
}
},4000);
System.out.println("开始计时");
}
}
3.代码里一些需要注意的地方
为什么扫描线程里判断队首元素时不直接peek?
因为peek不带有阻塞功能,只有put和take带有阻塞功能.
如果直接peek队首元素,如果扫描线程先执行,那么队列为空就会报空指针异常:
为什么在扫描线程内部调用wait方法?
代码的逻辑如果是不加wait,如果执行时间还没到,那么就得放回去,然后进入while循环继续取队首元素进行判断,假设我设置的是2个小时之后执行,那么在这两个小时之前就会一直在对队首元素进行判断,也就是在进行忙等,忙等会一直占用CPU,但是这有违背定时器的初衷.
因此加上带有时间的wait,就可以有效的避免忙等.并且在wait等待期间,如果queue.put又增加任务,queue.put后的notify会将wait唤醒,扫描线程会再一次判断队首元素的执行时间,如果优先级阻塞队列队首的任务的执行时间更早了,那么等待时间就会重新刷新.
调用wait方法就得上锁,锁范围的问题
漏掉任务的情况:
出现死锁的情况:
🌏🌏🌏今天的你看懂这里又学到了很多东西吧🌏🌏🌏
🌔 🌔 🌔下次见喽🌔 🌔 🌔