定时器:Timer类
常用方法方法:
1.schedule(TimeTask timetask,long delay,(long period)):
TimeTask:实现了Runnable类,实现时需要重写run方法
delay:表示延迟多少(decay)后开始执行任务,单位是毫秒,这个参数也可以是日期(Date)
period:周期时间,表示定时器循环执行任务之间的间隔时间,时间是毫秒
如果没有period参数,那么就只执行一次任务内容
2.scheduleAtFixedRate(TimeTask timetask,long delay,long period):
参数和schedule的参数相同,其作用为定时器设置循环执行的内容,第一次执行内容的延迟时间,循环的周期时间。可以看出schedule方法的功能其实已经包括这个方法了
3.cancel():关闭计时器
schedule(TimeTask timetask,long delay)
public static void main(String[] args) {System.out.println("任务三秒后开启");Timer t = new Timer();//定时执行任务 表示几秒后执行run方法里面的内容t.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("任务开启");}},3000);}
schedule(TimeTask timetask,long delay,long period):
public static void main(String[] args) {System.out.println("任务三秒后开启");Timer t = new Timer();//定时执行任务 表示几秒后执行run方法里面的内容t.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("开始以1秒的时间间隔循环执行任务");}},2000,1000);}
scheduleAtFixedRate(TimeTask timetask,long delay,long period):
public static void main(String[] args) {System.out.println("任务两秒后开启");Timer t = new Timer();//定时执行任务 表示几秒后执行run方法里面的内容t.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("任务开启");}},2000);t.scheduleAtFixedRate(new TimerTask() {@Overridepublic void run() {System.out.println("开始循环任务(间隔1s)");}},2000,1000);}
指定定时器执行固定个任务后结束
public static void main(String[] args) {Timer t = new Timer();int timeCount = 5;long delay = 1000;long period = 1000;t.schedule(new TimerTask() {int count = 1;@Overridepublic void run() {if(count >= timeCount){t.cancel();}System.out.println("执行任务:"+count);count++;}},delay,period);}
模拟实现定时器
import java.util.concurrent.BlockingQueue; import java.util.concurrent.PriorityBlockingQueue;class Task implements Comparable<Task>{//这个类用来描述任务的private Runnable runnable;//执行的任务时间private long time; //time + System.currentTimeMillis()public Task(Runnable runnable,long time){this.runnable = runnable;this.time = time;}public long getTime(){return time ;}public void run() {runnable.run();}//比较规则 执行时间在前的先执行@Overridepublic int compareTo(Task o) {return (int)(this.time - o.time);} } public class MyTimer{//一个阻塞队列private BlockingQueue<Task> queue = new PriorityBlockingQueue<>();//扫描线程private Thread t = null;public MyTimer(){t = new Thread(() -> {while(true) {try {Task task = queue.take();if (task.getTime() > System.currentTimeMillis()) {//还没到时间queue.put(task);} else {//执行任务task.run();}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}public void schedule(Runnable runnable,long time) throws InterruptedException {//在这一步加System.currentTimeMillis() 不能在getTime方法中加,不然就会一直大于System.currentTimeMillis()Task task = new Task(runnable, time+System.currentTimeMillis());queue.put(task);}public static void main(String[] args) throws InterruptedException {MyTimer m = new MyTimer();m.schedule(new Runnable() {@Overridepublic void run() {System.out.println("这是任务1");}},2000);m.schedule(new Runnable() {@Overridepublic void run() {System.out.println("这是任务2");}},1000);} }
问题:
1.忙等
假设此时时间是上午9点,第一个任务的时间是上午10点,那么在这一个小时内,程序会一直重复执行一个代码:
Task task = queue.take();if (task.getTime() > System.currentTimeMillis()) {//还没到时间queue.put(task);}
这行代码可能会循环个非常多亿次,且别忘了,优先级队列的底层是用堆实现的,每当我们取出一个元素又出现插入时,根据堆的调整,此元素又会在堆顶,而每一层调整都是有开销的,故这一时间段的开销是重复且多余的,所以我们就等一次,等堆顶任务执行时间与当前时间的差值就可,修改代码:
Task task = queue.take();if (task.getTime() > System.currentTimeMillis()){//还没到时间queue.put(task);Thread.sleep(task.getTime() - System.currentTimeMillis());
大伙们是不是觉得此时的代码就已经完美了?!!
然而并不是! 我们并不能使用sleep休眠来休眠两时间差
试想一下,如果咱们休眠的时候突然插进来了一个上午9.30执行的任务,那么这个任务就不会被执行到!故使用带参版本的wait()方法才是最好的选择
再次修改代码:
if (task.getTime() > System.currentTimeMillis()){//还没到时间queue.put(task);synchronized (this){this.wait(task.getTime() - System.currentTimeMillis());}public void schedule(Runnable runnable,long time) throws InterruptedException {Task task = new Task(runnable, time+System.currentTimeMillis());queue.put(task);//唤醒t线程里的wait操作this.notify();}
这个时候代码看起来是不是似乎万无一失了已经!
but,我们来考虑一个极端极端极端极端的情况
2.极端情况
if (task.getTime() > System.currentTimeMillis()){//还没到时间queue.put(task);synchronized (this){this.wait(task.getTime() - System.currentTimeMillis());}
如果线程1在执行到queue.put(task)时,恰好被调度走了,此时另一个线程调用schedule方法,且如果当线程2的任务时间小于线程1的任务时间时,此时线程2的任务就不会执行到。因为线程2的notify没作用,线程1都还没有执行到wait方法,但线程1重新执行时,此时的时间差已经是固定了的,但是这个时间差要大于线程2任务执行的时间,故线程2就会被“极端”地错过
完美代码
加大锁的力度:造成此极端情况的原因即为:take和wait是多步操作,非原子性
public MyTimer(){t = new Thread(() -> {while(true) {synchronized (this){try {Task task = queue.take();if (task.getTime() > System.currentTimeMillis()) {//还没到时间queue.put(task);this.wait(task.getTime() - System.currentTimeMillis());} else {//执行任务task.run();}} catch (InterruptedException e) {e.printStackTrace();}}}});t.start();}