定时器(java)
文章目录
- 什么是定时器
- 标准库中的定时器
- 实现定时器
什么是定时器
定时器是一种编程工具,用于特定时间或固定时间间隔执行任务(代码逻辑),其核心功能是管理任务的调度和执行
标准库中的定时器
- Timer 和 TimerTask 类
定位:早期的单线程定时器实现,适用于简单场景。
核心类:
Timer:用于调度任务。
TimerTask:抽象类,需继承并实现 run() 定义任务逻辑。
实现定时器
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
class Mytask implements Comparable<Mytask>{
private Runnable runnable;
// 定义任务执行时间
private long time;
public Mytask(Runnable runnable,long delay){
if(runnable==null){
try{
throw new IllegalAccessException("任务不能为空");
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
if(delay<0){
try {
throw new IllegalAccessException("时间不能小于0");
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
this.runnable=runnable;
//计算出任务的具体时间
this.time=delay+System.currentTimeMillis();
}
public Runnable getRunnable() {
return runnable;
}
public long getTime() {
return time;
}
@Override
public int compareTo(Mytask o) {
if(this.getTime()-o.getTime()>0)return 1;
else if(this.getTime()-o.getTime()==0)return 0;
else return -1;
}
}
public class MyTimer {
//定义线程内容
private PriorityBlockingQueue<Mytask> priorityBlockingQueue=new PriorityBlockingQueue<>();
public MyTimer() {
//扫描线程
Thread th1 = new Thread (()-> {
while (true) {
try {
Mytask take = priorityBlockingQueue.take();
//判断有没有到时间
long times = System.currentTimeMillis();
if (times >= take.getTime()) {
//时间到了执行任务
take.getRunnable().run();
} else {
long waittime = take.getTime() - times;
//没有到时间放回阻塞队列
priorityBlockingQueue.put(take);
synchronized (this) {
//进行休眠
this.wait(waittime);
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
th1.start();
//创建一个后台线程
Thread thread = new Thread (()->{
while (true) {
synchronized (this) {
notifyAll();
}
//休眠一会
try {
TimeUnit.MICROSECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
//设置为后台线程
thread.setDaemon(true);
thread.start();
}
public void schedule(Runnable runnable,long time) {
//构建一个Mytask对象
Mytask mytask=new Mytask(runnable,time);
//放入阻塞数组
priorityBlockingQueue.put(mytask);
//唤醒等待线程
synchronized (this){
this.notifyAll();
}
}
}
这里要注意当将锁在加在整个while(扫描线程)时会产生死锁
/扫描线程
Thread th1 = new Thread (()-> {
synchronized (this) {
while (true) {
try {
Mytask take = priorityBlockingQueue.take();
//判断有没有到时间
long times = System.currentTimeMillis();
if (times >= take.getTime()) {
//时间到了执行任务
take.getRunnable().run();
} else {
long waittime = take.getTime() - times;
//没有到时间放回阻塞队列
priorityBlockingQueue.put(take);
//进行休眠
this.wait(waittime);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
引起死锁的原因
锁的持有与阻塞调用:
扫描线程 th1 的整个循环被包裹在 synchronized(this) 块中。
当队列为空时,priorityBlockingQueue.take() 会阻塞,但此时 th1 仍持有 this 锁。
其他线程(如主线程调用 schedule)需要获取 this 锁才能添加任务,但无法获得锁,导致它们被阻塞。
互相等待的僵局:
th1 在等待队列中有任务(被 take() 阻塞),但其他线程无法添加任务(因为它们需要 this 锁)。
其他线程在等待 th1 释放锁,而 th1 在等待其他线程添加任务,形成死锁。
1.synchronized和wait的使用:
在MyTimer类的构造方法中,扫描线程t1并使synchronized(this)进行同步,台线程 (thread) 也使用 synchronized (this) 来调用 notifyAll()。
2.wait 和 notifyAll 的逻辑
在从队列中拿到线程任务时如果线程任务还没有到会调用wait()进行等待,
在主线程
后台线程 (thread) 也在尝试调用 notifyAll(),但它是在 synchronized (this) 块中执行的。如果此时没有其他线程持有 MyTimer 对象的锁,后台线程将无法执行 notifyAll()。所以锁得不到释放
任务的执行时间:
3.如果 Mytask 的执行时间设置得非常短(例如 0 毫秒),可能会导致 th1 线程不断地将任务放回队列并调用 wait(),而后台线程可能无法及时调用 notifyAll(),从而导致 th1 永远处于等待状态。