设置一个时间,当时间到了的时候,定时器就会去自动执行某个逻辑.
timer简介
1.创建计时器
Timer timer=new Timer();//创建计时器
2.创建任务
timer.schedule(new TimerTask(){//接口,这里不能使用lambada表达式,lambada表达式要求接口只有一个函数,这里有多个
public void run(){
System.out.println("hello 1000");
}
},1000);//第二个參数是推迟时间,delay,计时器创建好之后,什么时候进行执行
3.关掉计时器
timer.cancel();//停掉计时器,未执行的任务停止
这里timer里面的任务执行完了也并不会结束,因为他并不知道是否还会添加新的任务进去,
处在严阵以待的状态
此时如果我们想要结束,应该使用cancel主动去结束.
完整代码:
package thread;
import java.lang.annotation.Target;
import java.util.Timer;
import java.util.TimerTask;
public class ThreadDemo27 {
public static void main(String[] args) throws InterruptedException {
Timer timer=new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 3000");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 2000");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 1000");
}
},1000);
System.out.println("hello main");
Thread.sleep(5000);
timer.cancel();
}
}
Timer里面的内容
1)需要有一个线程,负责掐时间,等任务到达合适的时间,这个线程就负责执行
还需要有一个队列/数组,能够保存所有schedule进来的任务
直观的想,就是这个线程需要不断的去扫描上述队列中的每个任务是否到时间了,到时间了就需要去执行.
在这里每个任务都是带有delay时间的,肯定是先执行时间小的,后执行时间大的,我们可以使用优先级队列,只需要关注队首元素是否到时间,如果到时间了,就需要进行执行,
在此处,我们使用PriortyQueue(线程不安全,需要我们主动加锁控制),
PriorityBlockingQueue(线程安全)在此处场景中,不太好控制,容易出问题
自定义计时器:
1.首先我们先定义一个类,用来存放时间和任务.
class MYTIMETASK{
private Runnable runnable;//此处为执行的任务代码
private long time;//此处为执行任务的时间,此处的时间是一个相对时间,此处的时间是一个ms级别的时间戳
//构造方法
//获取时间的方法
public long getTime(){
return this.time;
}
//提供任务执行的接口
public void run(){
this.runnable.run();
}
public MYTIMETASK(Runnable runnable,long delay){
this.runnable=runnable;
this.time=System.currentTimeMillis()+delay;
}
}
但是我们在定义计时器MYTIME这个类的时候,需要用一个优先级队列来进行存储task任务,因此task必须是可以进行比较的,不然会报错,因此,task这个类就要实现Comparable这个接口
这是改进后的MTTIMETASK类
/**
* 用来存放执行的时间和任务
*/
class MYTIMETASK implements Comparable<MYTIMETASK>{
private Runnable runnable;//此处为执行的任务代码
private long time;//此处为执行任务的时间,此处的时间是一个相对时间,此处的时间是一个ms级别的时间戳
//构造方法
//获取时间的方法
public long getTime(){
return this.time;
}
//提供任务执行的接口
public void run(){
this.runnable.run();
}
public MYTIMETASK(Runnable runnable,long delay){
this.runnable=runnable;
this.time=System.currentTimeMillis()+delay;
}
@Override
public int compareTo(MYTIMETASK o) {
return (int)(this.time-o.time);//这里是long类型,要进行强制类型转换
}
}
这是MYTIMER类的实现
class MYTIMER{
private Thread t=null;//定义一个线程来不断地进行扫描,看是否有任务到时间了,不断地去扫描
private PriorityQueue<MYTIMETASK>queue=new PriorityQueue<>();//此处定义一个优先级队列来存放任务,
//但是这里需要注意,我们的优先级队列在进行存储自定义数据时,需要实现Comparable接口方法.,因此上述代码应做出改动
public void schedule(Runnable runnable,long delay){//使用schedule进行任务的添加
MYTIMETASK task=new MYTIMETASK(runnable,delay);
queue.offer(task);//进行任务的添加
}
//在构造方法里面创建一个线程来进行扫描,严阵以待
public MYTIMER(){
t=new Thread(()->{
while(true){
if(queue.isEmpty())
{
continue;//如果队列为空,则不能进行peek,要循环到有任务进来了,才可以执行当前逻辑
}
//如果不为空,我们则需要查看队列顶部的任务是否到了执行的时间
MYTIMETASK task=queue.peek();
if(System.currentTimeMillis()>=task.getTime()){
task.run();//到了执行任务的时间,则需要进行执行
queue.poll();//执行了则需要进行出队列操作
}
}
});
t.start();//开启这个线程
}
}
但是这里面仍然有不足的地方..
首先这是一个多线程,我们就要查看是否设计线程安全问题.
当构造方法的t线程和主线程在调用schedule方法时,两个线程会同时对queue这个队列进行操作.
如上图而言,这样就会出现线程不安全,因此我们要给他们进行加锁.
加上锁了之后,线程是安全了,但是还有一个问题.
当队列一开始为空的时候,有图中的线程会一直执行while循环,每次都会快速的解锁,但是他每次都会先一步左边的线程拿到锁,因为左边的锁是处于沉睡的状态,才被唤醒会竞争不过右边的线程,那么就会造成一直都是右边的线程拿到锁,但是又无法继续执行下去.这种情况,我们就应该使用wait方法,让右边的线程主动进入WAITING阻塞状态,主动放弃锁的竞争,给左边的线程一点时间,等到左边的线程放进了任务的时候,再去通知右边线程去执行.
相同的道理,如果此时右侧队列的第一个任务的时间过于长的话,他就会一直争夺锁,因此我们也要给他设置一个带时间的wait方法,不然左边的线程在他一直循环争夺锁的过程是拿不到锁的.
最终完整代码:
/**
* 用来存放执行的时间和任务
*/
class MYTIMETASK implements Comparable<MYTIMETASK>{
private Runnable runnable;//此处为执行的任务代码
private long time;//此处为执行任务的时间,此处的时间是一个相对时间,此处的时间是一个ms级别的时间戳
//构造方法
//获取时间的方法
public long getTime(){
return this.time;
}
//提供任务执行的接口
public void run(){
this.runnable.run();
}
public MYTIMETASK(Runnable runnable,long delay){
this.runnable=runnable;
this.time=System.currentTimeMillis()+delay;
}
@Override
public int compareTo(MYTIMETASK o) {
return (int)(this.time-o.time);//这里是long类型,要进行强制类型转换
}
}
/**
* 计时器,用来判断当前哪一个任务该执行了
*/
class MYTIMER{
private Thread t=null;//定义一个线程来不断地进行扫描,看是否有任务到时间了,不断地去扫描
private PriorityQueue<MYTIMETASK>queue=new PriorityQueue<>();//此处定义一个优先级队列来存放任务,
//但是这里需要注意,我们的优先级队列在进行存储自定义数据时,需要实现Comparable接口方法.,因此上述代码应做出改动
private Object locker1=new Object();
public void schedule(Runnable runnable,long delay){//使用schedule进行任务的添加
synchronized (locker1) {
MYTIMETASK task = new MYTIMETASK(runnable, delay);
queue.offer(task);//进行任务的添加
locker1.notify();
}
}
//提供一个cancel方法来进行计时器任务的结束
public void cancel(){
t.interrupt();
}
//在构造方法里面创建一个线程来进行扫描,严阵以待
public MYTIMER(){
t=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
try {
synchronized (locker1) {
while (queue.isEmpty()) {
locker1.wait();
//continue;//如果队列为空,则不能进行peek,要循环到有任务进来了,才可以执行当前逻辑
}
//如果不为空,我们则需要查看队列顶部的任务是否到了执行的时间
MYTIMETASK task = queue.peek();
if (System.currentTimeMillis() >= task.getTime()) {
task.run();//到了执行任务的时间,则需要进行执行
queue.poll();//执行了则需要进行出队列操作
}
else{
locker1.wait(task.getTime()-System.currentTimeMillis());
//此处的wait有两种方式,一种是当没有任务添加进来的时候,这个线程会一直等到//时间到了继续再去执行,
//另外一种是又有新添加的任务,这个线程要扫描当前任务时间最近的线程,这个新添加的任务就有可能是最近时间最接近的任务
}
}
}
catch (InterruptedException e){
break;
}
}
});
t.start();//开启这个线程
}
}
测试代码:
public class Main{
public static void main(String[] args) throws InterruptedException {
MYTIMER timer=new MYTIMER();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 3000");
}
},3000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 2000");
}
},2000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 1000");
}
},1000);
Thread.sleep(5000);
timer.cancel();
}
}
运行结果:
文章来源:https://www.toymoban.com/news/detail-755427.html
以上就是计时器的自我实现,感谢各位大佬的三连.文章来源地址https://www.toymoban.com/news/detail-755427.html
到了这里,关于JAVAEE-定时器案例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!