您的位置:首页 > 科技 > 能源 > 动漫设计就业前景如何_网络营销专业分析_成都百度关键词排名_代发软文

动漫设计就业前景如何_网络营销专业分析_成都百度关键词排名_代发软文

2025/4/8 10:23:54 来源:https://blog.csdn.net/Zz_waiting/article/details/147028333  浏览:    关键词:动漫设计就业前景如何_网络营销专业分析_成都百度关键词排名_代发软文
动漫设计就业前景如何_网络营销专业分析_成都百度关键词排名_代发软文

定时器,是我们日常开发所常用的组件工具,类似于闹钟,设定一个时间,当时间到了之后,定时器可以自动的去执行某个逻辑

目录

Timer 的基本使用

实现一个 Timer

通过这个类,来描述一个任务

通过这个类,来表示一个定时器

MyTimer 构造方法,创建扫描线程,让扫描线程来完成判定和执行

问题一: 线程安全问题

第二个问题:如果一直执行 while 那就一直在加锁和解锁...

完整代码如下:

流程图: 

执行顺序解析:

注意:


Timer 的基本使用

Java 标准库中,也提供了定时器的实现。

创建一个 timer 对象之后,调用 timer 的schedule 方法,在schedule 方法中,参数是一个匿名内部类,重写 run 方法,run 方法的方法体中,就是我们时间到了之后要执行的代码,还有一个参数是我们要等待的时间(单位是ms)(这里不可以使用 lambda 表达式。lambda 表达式得是函数时接口才行,即 interface 里面只能有一个方法)

定义一个 timer 添加多个任务,多个任务同时会带有一个时间:

打印结果如下:

且当打印完成之后,进程并不会结束,Timer 里内置了一个线程(前台线程)

timer 并不知道我们的代码是否还会添加新的任务进来,处在“严阵以待”的状态,我们需要使用 cancel 来主动结束,否则 Timer 是不知道是否其他地方还要继续添加任务的。

实现一个 Timer

Timer 里面要包含那些内容呢?

        1. 需要一个线程,负责帮我们来掐时间。等任务到达合适的时间,这个线程就负责执行。

        2. 还需要一个队列 / 数组,能够保存所有 schedule 进来的任务。

直观想,如果这个线程,不停的去扫描上述队列中的每个元素,看每个任务的时间否达到,到时间就执行。(但如果这个队列很长,这个遍历的过程的开销就很大了 O(N) )

==》 优先级队列!!!

每个任务都是带有 delay 时间的,一定是先执行时间小的,后执行时间大的。就不需要对上述队列进行遍历了,只需要关注队首元素是否到时间。(如果队首没到时间,后续其他元素也就一定没到时间) ==》 可以使用标准库提供的PriorityQueu(线程不安全),也有 PriorityBlockingQueue(线程安全)(但在我们此处的场景中,PriorityBlockingQueue 不太好控制,容易出问题)(我们可以对 PriorityQueue进行手动加锁,确保线程安全)

开始实现:

通过这个类,来描述一个任务

时间戳:以 1970 年 1 月 1 日 0 时 0 分 0 秒,为基准,计算当前时刻和基准时刻的 秒数 / 毫秒数 / 微秒数...

delay 是一个“相对”的时间间隔,也就是以当前时刻为基准,计算 3000 ms 之后才执行。

针对上面的类,还有什么比较重要的问题呢???==》 我们上面的类,是要放在优先级队列中的。有些集合类,是对元素有特定要求的(PriorityQueu TreeMap TreeSet 都是要求元素是“可比较大小的” ==》 Comparable,Comparator)(HashMap,HashSet 则是要求元素是“可比较相等” “可 hash 的”==》 equals hashCode) 

实现 Comparable 接口,重写 compareTo 方法

此处是期望根据时间,时间的作为优先级更高...(当我们要使用 compareTo 的时候,千万不要背到底是那个 - 那个,直接写代码试一试即可!!! ==》 作为程序员,要“扬长避短”,记忆这个事情,并不是我们人脑擅长的...)

// 通过这个类,来描述一个任务
class MyTimerTask implements Comparable<MyTimerTask> {// 在什么时间点来执行这个任务// 在此约定这个 time 是一个 ms 级别的时间戳private long time;// 实际任务要执行的代码:private Runnable runnable;//delay 是一个 “相对时间”public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;//计算一下真正要执行任务的绝对时间//(使用绝对时间,方便判定任务是否到达时间)this.time = System.currentTimeMillis() + delay;}public void run() {runnable.run();}@Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time - o.time);}public long getTime() {return time;}
}

通过这个类,来表示一个定时器

其中定义了 t 线程,负责扫描任务队列,执行任务。

用优先级队列创建一个 queue,其中元素存放 MyTimer(我们刚刚创建的任务类)

在 schedule 方法中,传入两个参数 runnable 和 delay,将 task 添加入 queue 当添加成功的时候, notify 锁

MyTimer 构造方法,创建扫描线程,让扫描线程来完成判定和执行

当前这个代码,还至少有两个核心问题,需要解决。

问题一: 线程安全问题

在主线程中,我们 new 新的 Timer 然后调用 schedule 方法来添加任务。

在 MyTimer 中,我们 new 了个优先级队列 queue 作为成员变量,但优先级队列是线程不安全的,需要进行加锁来解决线程安全问题。

我们是在另一个线程中,即 MyTimer 的构造方法中的 t 线程中,对队列进行删除,应该在这个线程中加锁

在这个线程中,synchronized 加在哪里呢?我们先大致观察,发现 t 线程中,都是写操作,为了要实现我们前面提到的原子性,就可以给整个线程加锁 ==》 

但这样加锁,是正确的吗???

我们在主线程中 new 一个 MyTimer 对象

然后进入 MyTimer 的构造方法:

构造方法就是 t 线程,这个线程进来之后,就直接加锁了,加锁之后,才会进入 while 循环,while 循环结束了之后,锁才能释放,但有没有一种可能,我们的 while 循环结束不了,导致我们的锁无法释放...

主线程下面的语句 myTimer 调用 schedule 方法

schedule 方法中也在尝试加锁,但锁被构造方法中占用着,这里就没办法加上锁了...

一番调整之后,我们可以把 synchronized 加在 while 里面,这样才会有释放锁的机会,外面才有可能拿到锁

第二个问题:如果一直执行 while 那就一直在加锁和解锁...

我们上面的代码,当队列为空,没有任务的时候,直接 continue,然后又 while,同样的,时间还没到,直接 continue,然后又 while,这里两个代码继续循环是没有意义的,应该等一等!!!

这里两个代码的执行速度是非常快的,当解锁之后,马上又会进入 while 循环,立即又重新尝试加锁了,导致其他线程想要通过 schedule 加锁,但是加不上(线程饿死

==》 解决方法:引入 wait

注意: 在当时间还没到,暂时不执行中,使用 sleep 是不太合适的。(可能我们会想,当 curTime >= task.getTime() 不满足的时候,那我们 sleep task.getTime() - curTIme 时间即可,但这样是不行的!)

但我们可以写为 locker.wait(task.getTime() - curTime) 

        1. wait 的过程中,有新的任务来了,wait 就会被唤醒!!!schedule 有 notify的!会根据新的任务重新计算要等待的时间

        2. wait 的过程中,没有新的任务,时间到了,按照原定计划,执行之前的这个最早的任务即可。

但 wait 我们在前面讲过,wait 可能存在“虚假唤醒”的情况,即在 wait 的等待过程中,发现了一些意料之外的情况,所以前面我们是用 while 来进行多次确定的,这里的话,我们可以再进行一次任务检查。 ==》 

这样,我们就成功实现了一个 Timer 

完整代码如下:

import java.util.PriorityQueue;// 通过这个类,来描述一个任务
class MyTimerTask implements Comparable<MyTimerTask> {// 在什么时间点来执行这个任务// 在此约定这个 time 是一个 ms 级别的时间戳private long time;// 实际任务要执行的代码:private Runnable runnable;//delay 是一个 “相对时间”public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;//计算一下真正要执行任务的绝对时间//(使用绝对时间,方便判定任务是否到达时间)this.time = System.currentTimeMillis() + delay;}public void run() {runnable.run();}@Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time - o.time);}public long getTime() {return time;}
}// 通过这个类,来表示一个定时器
class MyTimer {// 负责扫描任务队列,执行任务的线程private Thread t = null;// 任务队列private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 锁对象private Object locker = new Object();public void schedule(Runnable runnable, long delay) {synchronized (locker) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);locker.notify();}}// MyTimer 构造方法,用来创建扫描线程,让扫描线程来完成判定和执行public MyTimer() {t = new Thread(() -> {// 扫描线程就需要循环的 反复的扫描队首元素,然后判定队首元素是不是时间到了// 如果时间没到,什么都不做// 如果时间到了,就执行这个任务 并且把这个任务从队列中删除while (true) {try {synchronized (locker) {while (queue.isEmpty()) {//如果队列为空,即没有任务// 先不处理locker.wait();}MyTimerTask task = queue.peek();// 读取到当前时间long curTime = System.currentTimeMillis();if (curTime >= task.getTime()) {// 当前时间已经到了任务时间,就可以执行任务了queue.poll(); // 先把任务从队列中出来task.run(); // 执行任务} else {// 当前时间还没到,暂时先不执行locker.wait(task.getTime() - curTime);if (curTime >= task.getTime()) {queue.poll();task.run();}}}} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();}
}public class ThreadDemo41 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 3000");}}, 3000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 2000");}}, 2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 1000");}}, 1000);}
}

流程图: 

执行顺序解析:

注意:

这里的 wait 之后,还需要再跟一个条件判断。

当我们加了再次条件检查之后,就算是被唤醒了之后,但因为有我们的条件检查,(当前时间还未达到任务执行时间),此时代码中虽然没有显示地再次调用 wait 方法让线程继续等待,但由于整个扫描线程的逻辑是在一个无限循环 while(true) 中,在这一轮循环结束后,会进入下一轮循环,在下一轮循环中,会再次检查任务队列的状态

当再次检查的时候,发现 curTine 仍然小于 task.getTime(),就会再次执行 locker.wait(task.getTime() - curTime)让线程继续等待,直到时间到达,或者再次被唤醒并满足条件。所以,虽然在唤醒后没有立即再次调用 wait 方法,但通过循环结构和条件判断,最终还是实现了线程在条件不满足的时候继续等待的逻辑。

还有就算,在我们的代码中,schedule 方法等待的时间,是相对于调用 schedule 方法时的当前时间而言的,在我们的示例代码中,第一个 schedule 方法调用之后,1000ms 之后会输出 “hello 1000”,第二个 schedule 方法调用之后,2000ms 之后会输出“hello 2000”,第三个 schedule 方法调用之后,3000ms 之后会输出“hello 3000”.

但是我们这里的三个 schedule 方法调用的时间几乎是同步,所以差不多就是在执行程序之后的 3s 之后会输出“hello 3000”

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com