Java多线程案例之任务定时执行器
⭐️前面的话⭐️
本篇文章将介绍Java多线程案例,定时器,定时器就像闹钟一样,等到了指定的时间,闹钟就会发出响声来提醒您,而定时器会执行指定的任务。
📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创
📆华为云首发时间:🌴2022年6月30日🌴
✉️坚持和努力一定能换来诗与远方!
💭参考书籍:📚《java核心技术》,📚《java编程思想》
💬参考在线编程网站:🌐牛客网🌐力扣
博主的码云gitee,平常博主写的程序代码都在里面。
博主的github,平常博主写的程序代码都在里面。
🍭作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!
🍎1.定时器概述
🍏1.1认识定时器
java中的定时器,也可以叫做任务定时执行器,顾名思义,就是等到了指定的时间,就去做指定的事情,就像你每周六或周日都要去力扣参加周赛一样。
所以你如果想要使用定时器,你需要交代时间和对应的任务才行,java标准也提供了带有定时器功能的类Timer
。
🍏1.2Timer类的使用
在java1.8中,Timer给出了四个构造方法,这些构造方法可以去指定线程的名字和是否将定时器内部的线程指定为守护线程。
好了,又出现了一个新概念,这个守护线程是什么鬼?
其实在java中有两种线程,一种是用户线程,另外一种是守护线程。用户线程就是普通的线程,守护线程顾名思义就是守护用户线程的线程,可以说就是用户线程的保姆,守护线程与JVM“共存亡”, 只要存在一个用户线程,程序中所有的守护线程都不会停止工作,直到最后一个用户线程执行完毕,守护线程才会停止工作。守护线程最典型的应用就是 GC (垃圾回收器),它就是一个非常称职的守护者。
🍊构造方法:
序号 | 构造方法 | 说明 |
---|---|---|
1 | public Timer() | 无参数构造方法,默认定时器关联的线程不是守护线程,线程名字也是默认值 |
2 | public Timer(boolean isDaemon) | 指定定时器中关联的线程是否为守护线程,如果是,参数为true |
3 | public Timer(String name) | 指定定时器关联线程名称,线程类型默认为非守护线程 |
4 | public Timer(String name, boolean isDaemon) | 指定定时器关联线程名和线程类型 |
Timer类构造时内部也会创建线程,如果不指定,定时器对象内部的线程(为了简化,就称为关联线程吧)的类型是用户线程,而不是守护线程。
🍊核心方法:
序号 | 方法 | 说明 |
---|---|---|
1 | public void schedule(TimerTask task, long delay) | 指定任务,延迟多久执行该任务 |
2 | public void schedule(TimerTask task, Date time) | 指定任务,指定任务的执行时间 |
3 | public void schedule(TimerTask task, long delay, long period) | 连续执行指定任务,延迟时间,连续执行任务的时间间隔,毫秒为单位 |
4 | public void schedule(TimerTask task, Date firstTime, long period) | 连续执行指定任务,第一次任务的执行时间,连续执行任务的时间间隔 |
5 | public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 与方法4作用相同 |
6 | public void scheduleAtFixedRate(TimerTask task, long delay, long period) | 与方法3作用相同 |
7 | public void cancel() | 终止定时器所有任务,终止执行的任务不受影响 |
🍊使用演示:
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;
public class TimeProgram {
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行延后2s执行的任务!");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行延后5s执行的任务!");
}
}, 5000);
//每秒输出一个mian
for (int i = 0; i < 5; i++) {
System.out.println("main");
Thread.sleep(1000);
}
}
}
🍊运行结果:
TimerTask类就是专门描述定时器任务的一个抽象类,它实现了Runnable接口。
public abstract class TimerTask implements Runnable //jdk源码
下面我们简单实现一下定时器,我们就不用TimerTask了,我们直接使用Runnable,因为TimerTask实现了Runnable接口,所以后面测试我们自己所写的schedule
方法时,也可以传入TimerTask类型的引用,既然是简单地实现,那就不实现连续执行的功能了。
🍎2.定时器的简单实现
首先,我们需要建造一个类,来描述定时器的任务,可以使用Runnable加上一个任务执行的时间戳就可以了。
🍊具体清单:
一个构造方法,用来指定任务和延迟执行时间。
两个获取方法,用来给外部对象获取该对象的任务和执行时间。
实现比较器,用于定时器任务对象的组织,毕竟,每次需要执行时间最早的任务,需要用到基于小根堆实现的优先队列,不,还需要考虑多线程的情况,还是使用优先级阻塞队列吧。
//我的任务
class MyTask implements Comparable<MyTask> {
//接收具体任务
private Runnable runnable;
//执行时的时间戳
private long time;
//构造方法
public MyTask(Runnable runnable, int delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
//执行任务
public void run() {
this.runnable.run();
}
//获取执行时间
public long getTime() {
return this.time;
}
//实现comparable接口,方便创建优先级阻塞队列
@Override
public int compareTo(MyTask o) {
return (int) (this.time - o.time);
}
}
接下来就要实现定时器类了,首先我们需要一个数据结构来组织定时器任务,并且每次都能将时间最早的任务找到并执行,那么这个数据结构非小根堆莫属了,也就是优先级队列,注意对自定义类使用优先级队列时,一定要实现比较器。
//每次执行任务时,需要优先执行时间在前的任务,即每次执行任务要选择时间戳最小的任务,在多线程情况中优先级阻塞队列是最佳选择
private static final PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();
然后,需要一个方法将任务安排在优先级阻塞队列中,最后在构造定时器对象的时候从优先级阻塞队列中取任务并在指定的时间执行。
按照上图的逻辑,我们自己实现的定时器类需要有一个线程专门去执行任务,执行任务过程中可能会遇到执行时间还没有到的情况,那么线程必须得等待,线程等待的方法有两种,一种是wait
另一种是sleep
,这个案例我们推荐前者,因为sleep
方法不能中途唤醒,这个案例是有可能需要中途唤醒的,那就是有新任务插入时,需要重新去优先级阻塞队列拿任务重复上述操作,这个唤醒操作可以使用notify
方法实现,所以需要用到wait/notify
组合拳,既然需要使用wait/notify
那么就得有锁,所以我们可以使用一个专门的锁对象来加锁。
🍊实现代码:
//我的定时类 用来管理任务
class MyTimer {
//专门对锁对象
private final Object locker = new Object();
//每次执行任务时,需要优先执行时间在前的任务,即每次执行任务要选择时间戳最小的任务,在多线程情况中优先级阻塞队列是最佳选择
private static final PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();
//安排任务
public void schedule(Runnable runnable, int delay) {
//将任务放入小根堆中
MyTask task = new MyTask(runnable, delay);
priorityBlockingQueue.put(task);
//每次当新任务加载到阻塞队列时,需要中途唤醒线程,因为新进来的任务可能是最早需要执行的
synchronized (locker) {
locker.notify();
}
}
public MyTimer() {
Thread thread = new Thread(() -> {
while (true) {
try {
//加载任务,确定执行时机
MyTask myTask = priorityBlockingQueue.take();
long curTime = System.currentTimeMillis();
//时间未到,将任务放回
if (curTime < myTask.getTime()) {
synchronized (locker) {
priorityBlockingQueue.put(myTask);
//放回任务后,不能立即就再次取该任务加载,需要设置一个再次加载的等待时间,建议使用wait带参数的方法
//因为wait方法可以使用notify进行中途唤醒,而sleep不能中途唤醒
int delay = (int)(myTask.getTime() - curTime);
locker.wait(delay);
}
} else {
System.out.println(Thread.currentThread().getName() + "线程收到任务,正在执行中!");
myTask.run();
System.out.println(Thread.currentThread().getName() + "线程执行任务完毕,正在等待新任务!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//不要忘了启动线程
thread.start();
}
}
🍊上面是我们实现定时器的代码,我们来测试一下:
import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;
public class TimeProgram {
public static void main(String[] args) throws InterruptedException {
MyTimer timer = new MyTimer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行延后2s执行的任务!");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行延后5s执行的任务!");
}
}, 5000);
//每秒输出一个mian
for (int i = 0; i < 5; i++) {
System.out.println("main");
Thread.sleep(1000);
}
}
}
🍊执行结果:
好了,任务定时执行器你学会了吗?
下期预告:Java多线程案例之线程池
- 点赞
- 收藏
- 关注作者
评论(0)