设计一个定时器

定时器的使用

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("程序开始运行");
    }
}

设计一个定时器_第1张图片
可以看到这里程序在执行完所有任务后并没有挂掉,这是因为,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方法

你可能感兴趣的:(java)