Java简单模拟实现Timer

        Timer中最常用的方法几乎就是schedule方法,需要传递两个参数,一个是TimerTask的实例对象,另一个是多久后运行,其中TimerTask是一个接口,其实现了Runnable接口,所以通过TimerTask创建的实现类实例对象中也是有run方法的。 

        如果任务特别多,创建多个线程造成的开销是很大的,所以只需要只有一个/一组工作线程,每次都找到这些任务中最先到达时间的任务这就涉及到先后的问题,排序比较低效,而且插入任务的时候也是个麻烦,最好的是使用小根堆,即优先级队列,考虑到线程安全,要使用优先级阻塞队列即PriorityBlockingQueue

创建自定义类记录执行任务与执行时间

在使用优先级阻塞队列时,要创建一个新的自定义类,用于记录要执行的任务,以及执行的时间,将该类的每个实例为单位put进优先级阻塞队列中:

class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    //为方便使用,使用绝对时间戳
    public long time;
    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //取当前时刻时间戳+delay,作为该任务实际开始执行的时间戳
        this.time = delay + System.currentTimeMillis();
    }
    @Override
    public int compareTo(MyTask o) {
        return (int)(time - o.time);
    }
}

这里实现了Comparable接口,是为了在优先级阻塞队列中进行正确的比较,当然也可以在创建优先级阻塞队列的时候传递比较器,比如使用lambda或者内部类的方法

简单模拟Timer

成员变量

locker用于后续上锁与释放锁,queue是一个优先级阻塞队列,默认是小根堆,因为MyTask实现了Comparable接口,所以这里不传递比较器

 Object locker = new Object();
    //优先级阻塞队列中存储MyTask方便查看任务与任务开始的时间
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>(
            /*11,((o1, o2) -> (int)(o1.time - o2.time))*/);

构造方法与schedule方法

  public void schedule(Runnable runnable, long delay){
        MyTask task = new MyTask(runnable,delay);
        queue.put(task);
        synchronized (locker){
            //如果MyTimer的前台线程在WAITING状态就将其唤醒,将堆顶的任务时间与新插入的任务的任务时间进行比较
            locker.notify();
        }
    }
    public MyTimer() {
        Thread thread = new Thread(() -> {
            while (true){
                try {
                    synchronized (locker) {
                        MyTask task = queue.take();
                        //Thread demo = new Thread(task.runnable);
                        /*这里如果采用创建线程的方法,就不会因为某个任务执行时间过长,使得后续任务延期*/
                        /*但是创建多个线程开销过大,所以采用调用run的方法,并没用真正创建线程*/
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.time){
                            //如果当前时间戳>=任务设定时的时间戳 + delay
                            task.runnable.run();
                            //demo.start();
                        }else {
                            //还没有到时间,就把拿出来的任务放回去,并且等待时长为时间差的一段时间
                            queue.put(task);
                            locker.wait(task.time - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }

上述代码中获取的时间戳是当前时刻与基准时刻的毫秒差,所以时间越往后,时间戳越大。基准时刻是1970年1月1日00:00:00

上锁位置问题

如果单独为了wait进行局部上锁,有可能会导致notify失效,从而导致任务执行时间安排的错乱

 总体效果:

import java.util.concurrent.PriorityBlockingQueue;

class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    //为方便使用,使用绝对时间戳
    public long time;
    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //取当前时刻时间戳+delay,作为该任务实际开始执行的时间戳
        this.time = delay + System.currentTimeMillis();
    }
    @Override
    public int compareTo(MyTask o) {
        return (int)(time - o.time);
    }
}
class MyTimer{
    Object locker = new Object();
    //优先级阻塞队列中存储MyTask方便查看任务与任务开始的时间
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>(
            /*11,((o1, o2) -> (int)(o1.time - o2.time))*/);
    public void schedule(Runnable runnable, long delay){
        MyTask task = new MyTask(runnable,delay);
        queue.put(task);
        synchronized (locker){
            //如果MyTimer的前台线程在WAITING状态就将其唤醒,将堆顶的任务时间与新插入的任务的任务时间进行比较
            locker.notify();
        }
    }
    public MyTimer() {
        Thread thread = new Thread(() -> {
            while (true){
                try {
                    synchronized (locker) {
                        MyTask task = queue.take();
                        //Thread demo = new Thread(task.runnable);
                        /*这里如果采用创建线程的方法,就不会因为某个任务执行时间过长,使得后续任务延期*/
                        /*但是创建多个线程开销过大,所以采用调用run的方法,并没用真正创建线程*/
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.time){
                            //如果当前时间戳>=任务设定时的时间戳 + delay
                            task.runnable.run();
                            //demo.start();
                        }else {
                            //还没有到时间,就把拿出来的任务放回去,并且等待时长为时间差的一段时间
                            queue.put(task);
                            locker.wait(task.time - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }
}

 运行效果

由于第一个安排的任务是一个死循环,所以后续两任务都是执行不到的,也就是说,并不是安排多久执行就多久执行,而是根据上传的时间参数和上一个任务结束的具体时间安排的,真正的Timer也是这样的

 class ThreadDemo11 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
               while (true){}
            }
        },1000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello2");
            }
        },2000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello3");
            }
        },3000);
    }
}

 如果把第一个安排的任务死循环去除,那么后续的任务是可以及时执行的

class ThreadDemo11 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
               System.out.println("hello1");
            }
        },1000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello2");
            }
        },2000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello3");
            }
        },3000);
    }
}

这里执行完3个任务,程序并没有真正停止,因为根据构造方法创造的thread因为在优先级阻塞队列内没有元素的情况下尝试take进入阻塞态,该线程并没有结束 ,而真正的Timer也是在执行完所有任务后,内部的线程不会停止,需要其他途径关闭

目录

创建自定义类记录执行任务与执行时间

简单模拟Timer

成员变量

构造方法与schedule方法

上锁位置问题

 总体效果:

 运行效果


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值