package Thread;
import java.util.Timer;
import java.util.TimerTask;
public class demo1 {
public static void main(String[] args) {
Timer timer =new Timer();
//给timer中注册的这个任务,不是在调用schedule的线程中执行的,而是通过Timer内部的线程,来负责执行的
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello3");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello2");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello1");
}
},1000);
System.out.println("程序开始运行");
}
}
可以看到这里程序在执行完所有任务后并没有挂掉,这是因为,Timer内部,有自己的线程,为了保证随时可以处置新安排的任务,这个线程会一直执行,并且这个线程是一个前台线程
一个定时器里,是可以有很多个任务的,先要把一个任务给描述出来,再使用数据结构来把多个任务组织起来.
1.创建一个TimerTask这样的类,来表示一个任务,这个任务,就需要包含两个方面,任务的内容和任务执行的时间,这里任务的时间需要利用时间戳来表示,在schedule的时候,先获取到当前的系统时间,在这个基础上,加上delay时间间隔,得到真实要执行这个任务的时间
2.使用一定的数据结构,把多个TimerTask给组织起来,使用优先级队列.来组织所有的任务是最合适不过的,因为队首元素就是时间最小的任务
3.做一个扫描线程,负责监控队首元素的任务执行的时间是否已到,以及调用这里的run方法完成任务
package Thread;
import com.sun.org.apache.bcel.internal.generic.NEW;
import java.util.PriorityQueue;
//创建一个类,用来描述定时器中的一个任务
class MyTimerTask implements Comparable<MyTimerTask> {
//任务什么时候执行,毫秒级别的时间戳
private long time;
//任务具体是什么
private Runnable runnable;
public MyTimerTask(Runnable runnable,long delay){
//delay是一个相对的时间差
//构造time要根据当前时间和delay进行构造
time= System.currentTimeMillis()+delay;
this.runnable=runnable;
}
public long getTime() {
return time;
}
public Runnable getRunnable() {
return runnable;
}
@Override
public int compareTo(MyTimerTask o) {
//时间小的优先级高
return (int)(this.time-o.time);
}
}
class Mytimer{
//使用优先级队列,来保存上述的N个任务
private Object locker = new Object();
private PriorityQueue<MyTimerTask> queue =new PriorityQueue<>();
//定时器的核心方法,就是要把要执行的任务添加到队列中
public void schedule(Runnable runnable,long delay){
synchronized (locker){
MyTimerTask myTimerTask =new MyTimerTask(runnable,delay);
queue.offer(myTimerTask);
//每次来新的任务,都唤醒一下之前的扫描线程,好让扫描线程根据最新的任务情况,重新规划等待时间
locker.notify();
}
}
//Mytimer中还需要构造一个"扫描线程",一方面去负责监控队首元素是否到时间了,是否应该执行,一方面,当任务到点之后,
//就要调用这里的Runnable的run方法来执行任务
public Mytimer(){
//扫描线程
Thread t = new Thread(()->{
while (true){
synchronized (locker){
while(queue.isEmpty()){
//注意,当前如果队列为空,此时就不应该去取这里面的元素
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
MyTimerTask myTimerTask =queue.peek();
long curTiem= System.currentTimeMillis();
if (curTiem>=myTimerTask.getTime()){
//假设当前时间是14.01,任务执行时间是14.00,就意味着应该要执行这个任务了
queue.poll();
myTimerTask.getRunnable().run();
}
else {
try {
locker.wait(myTimerTask.getTime()-curTiem);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
t.start();
}
}
//写一个定时器
public class demo2 {
public static void main(String[] args) {
Mytimer mytimer =new Mytimer();
mytimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello3");
}
},3000);
mytimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello2");
}
},2000);
mytimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello1");
}
},1000);
}
}
问题:
1.可能会存在线程安全问题
这个集合类,不是线程安全的,因为他既会在主线程中使用,又会被扫描线程使用
所以我们需要对有修改queue的地方进行加锁
2.线程扫描中,为什么不使用sleep进行休眠呢?
1)sleep在进入阻塞后,不会释放锁, 会影响到其他线程执行这里的schedule
2)sleep在休眠的过程中,不方便提前中断(interrupted虽然可以中断sleep,但是这个时候也意味着线程应该要结束了)
3.能放在优先级队列中的元素,都必须是可比较的,因此,我们需要实现comparable接口,并且重写compareTo方法