阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!
目录
一:什么是定时器
前引:定时器,顾名思义,就是我们建立一个多久时间后需要执行的任务
打个比方,现在是早上8点,我告诉闹钟2个小时后提醒我该去上课了~,到10点的时候,闹钟就执行这个任务——“提醒我去上课”
代码示例:
package thread; import java.util.Timer; import java.util.TimerTask; /** * Created with IntelliJ IDEA. * Description: * User: Hua YY * Date: 2024-09-26 * Time: 8:44 */ public class ThreadDemon32 { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("执行任务1,时间为1秒后"); } }, 1000); timer.schedule(new TimerTask(){ @Override public void run() { System.out.println("执行任务2,时间为2秒后"); } },2000); timer.schedule(new TimerTask(){ @Override public void run() { System.out.println("执行任务3,时间为3秒后"); } },3000); } }
二:IDEA中的定时器Timer
1:实例化Timer
编辑
2:.schedule()方法
(1)分析
编辑
注意导包
(2)具体实现
编辑
编辑
3:Timer内部前台线程
从上面的运行结果不难发现,我们的main函数执行完毕了,任务也都打印出来了,但是进程还在运行,就是因为Timer内部自带有前台线程
三: 自己实现定时器
1:思路
(1)阻塞队列放任务
首先需要一个阻塞队列来放所有schedule里的任务
注:这个阻塞队列一定是一个优先级队列,通过比较器来比较,delay的大小,来安排队列顺序,执行时间小的放前面,大的放后面
PriorityQueue(线程不安全,但是可以手动加锁)
PriorityBlockingQueue(线程安全,但是不太好人为控制)
(2)线程扫描任务
然后需要一个线程来扫描任务队列,负责计时,保证到时间了,任务出队列,来执行
四:代码超详细解读
(看不懂的按照步骤自己先敲一遍)(难度很大,敲完了会爽的起飞~~~)
package thread; import java.util.Comparator; import java.util.PriorityQueue; /** * Created with IntelliJ IDEA. * Description: * User: Hua YY * Date: 2024-09-26 * Time: 9:36 */ //1:首先创建一个定时器类 class MyTimer{ //2:需要一个线程来扫描队列(只需要指向队列中的队首元素) private Thread t = null; //3:创建一个优先级队列,思考创建完了,就得放任务进去 //10:把任务作为泛型放进队列 private PriorityQueue<MyTimerTask> queue = new PriorityQueue(); //23:这里main方法中添加任务和MyTimer中出任务会引发多线程安全问题,这里我们使用锁对象来保护队列,那么在哪里加锁比较合适呢 Object locker = new Object(); //13:需要去写一个方法,把参数传入 public void schedule(Runnable runnable , long delay){ //24.5:参与到锁竞争中的schedule也需要加上锁,因为涉及到队列offer操作 synchronized(locker){ //14:通过MyTimerTask构造 MyTimerTask task = new MyTimerTask(runnable , delay); //15:再把任务放到队列里面 queue.offer(task); //26:offer后有元素了,唤醒 locker.notify(); } } //16:通过构造方法,让t线程扫描判断队首任务是否到达时间 public MyTimer(){ t = new Thread(()->{ //17:t线程去扫描队首元素,如果时间到就删除队首元素,并执行任务,如果没到就等待 while(true){ //24:加锁,得加到while循环里面来,如果加到外面,当我们在main方法中newMyTimer的时候 //就会调用构造方法加锁,然后一直while循环出不来,解不了锁,进而参与锁竞争中的schedule就解不了锁 try{ synchronized(locker){ //17.5:如果队列为空等待 //27:While和notify捆绑使用,所以把if修改为while保险点,不懂的看阿华写的前面的文章哈 while (queue.isEmpty()){ //25:队列为空wait,catch异常,我们把try放到最外面 locker.wait(); } //18:peek一下,获取队首元素 MyTimerTask task = queue.peek(); //19:记录下当前绝对时间 long curTime = System.currentTimeMillis(); //20:比较当前绝对时间和任务要执行的绝对时间大小 if(curTime >= task.getTime()){ //21:若大于等于,则出队列执行 queue.poll(); task.run();//此处顺序无所谓 }else { //22:未到时间则等待 /*continue;*/ //28:省下资源 locker.wait(task.getTime() - System.currentTimeMillis()); } } }catch (Exception e){ e.printStackTrace(); } } }); t.start(); } } //4:通过MyTimerTask这个类来描述一个任务 class MyTimerTask implements Comparable<MyTimerTask>{ //5:任务执行时间(绝对时间,ms级别的时间戳) private long time; //6:具体要执行的任务(runnable) private Runnable runnable; //20:获取一下任务执行的绝对时间 public long getTime(){ return time; } //7:写构造方法对成员变量初始化 public MyTimerTask(Runnable runnable , long delay){ //8:time(绝对时间 = 当前时间 + 设置的倒计时) this.time = System.currentTimeMillis() + delay ; this.runnable = runnable; } //9:通过一个方法来执行任务(调用runnable中的run方法) public void run(){ runnable.run(); } //11:实现接口,写比较器(导java.lang这个包) @Override public int compareTo(MyTimerTask o) { //12:返回时间小的那个任务(多试试)——至此为第一部分 return (int)(this.time - o.time); } } public class ThreadDemon33 { public static void main(String[] args) { MyTimer timer = new MyTimer(); timer.schedule(new Runnable() { @Override public void run() { System.out.println("第三次测试,等待的相对时间为三秒"); } },3000); timer.schedule(new Runnable() { @Override public void run() { System.out.println("第一次测试,等待的相对时间为一秒"); } },1000); timer.schedule(new Runnable() { @Override public void run() { System.out.println("第二次测试,等待的相对时间为二秒"); } },2000); System.out.println("hello main"); } }
编辑
五:多线程安全问题
问题一:进出元素安全问题
(1)添加和删除队列元素引起的线程安全问题——和while死锁
编辑
(2)解决方式
编辑
问题二:线程饿死问题
读懂上面的图之后,我们继续看哈
因为while循环执行的太快,我们刚解完锁,schedule还没拿上锁,就又被while循环里面给锁上了
所以我们引入wait,那么进而就得有notify,那么谁等待谁通知呢——队列为空wait,队列offer了就notify
编辑
问题三:wait和while捆绑使用
if改为while
编辑
问题四:忙等
当前执行时间还没到,即进入else语句中,如果里面是continue,则会跳出语句,重新循环,此过程非常的快,非常占用cpu的资源,然而还没什么卵用,cpu这就是“瞎忙活”——简称“忙等”
我们要做的就是在等待过程中,想办法释放cpu资源
这里用sleep不合适 编辑
编辑
用wait()带有超时时间的版本
情况①:
在wait期间,如果有新的任务添加进来,那我们的schedule就会唤醒wait,然后wait重新计算需要等待的时间
情况②:
在wait期间,没有新的任务天剑进来,那wait就会一直等待到,任务需要执行的绝对时间(这就是带有超时时间的版本的好处)自己唤醒自己
编辑
编辑
六:无注释版本全代码
package thread; import java.util.PriorityQueue; class MyTimer {//计时器中的线程负责扫描队列任务 private Thread t = null; public Object locker = new Object(); private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(); public void schedule(Runnable runnable , long delay){ synchronized(locker){ MyTimerTask task = new MyTimerTask(runnable , delay); queue.offer(task); locker.notify(); } } public MyTimer(){//t线程去扫描队列,判断是否要执行任务 t = new Thread(()->{ while (true){ try{ synchronized(locker){ while(queue.isEmpty()){ locker.wait(); } //不为null拿到队首元素 MyTimerTask task = queue.peek(); if (System.currentTimeMillis() >= task.getTime() ){ queue.poll(); task.run(); }else { locker.wait(task.getTime()-System.currentTimeMillis()); } } } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); } } class MyTimerTask implements Comparable<MyTimerTask>{//描述一个任务,并把这个任务扔到队列里面去 private long time;//绝对时间 private Runnable runnable; public long getTime(){ return time; } public MyTimerTask(Runnable runnable , long delay){ this.time = System.currentTimeMillis() + delay; this.runnable = runnable; } public void run(){ runnable.run(); } @Override public int compareTo(MyTimerTask o) { return (int)(this.time - o.time); } } public class ThreadDemon33_2 { public static void main(String[] args) { MyTimer timer = new MyTimer(); timer.schedule(new Runnable() { @Override public void run() { System.out.println("任务一1s"); } },1000); timer.schedule(new Runnable() { @Override public void run() { System.out.println("任务二2秒"); } },2000); System.out.println("main函数"); } }