多线程——定时器

        定时器是一种能够指定任务在多久时间后执行的组件。

        Java标准库里的定时器

        在Java标准库中,提供了一个定时器类 Timer,Timer的核心方法为schedule。

        schedule包含两个参数,一个是要执行的代码,另一个是多久时间后执行(ms)。

public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 1000");
            }
        },1000);
        System.out.println("hello main");
    }

        

        可以观察到,运行代码后,立刻打印出了hello main等待一秒和三秒后打印出了hello 1000和hello 3000,并且此时 代码没有结束运行.

        因为在timer创建后,timer的内部有一个扫描线程,负责扫描schedule进来的代码和时间,等待时间到后这个线程会负责执行代码内容。但是timer不知道后续是否还会schedule进来内容,所以这个线程会一直扫描,不会停止。

        想要主动停止timer就要使用到另一个方法:cancel,使用这个方法后,不论现在是否有代码需要执行,timer都会立刻停止。

        实现定时器

        Java标准库的计时器使用了两个类Timer和TimerTask,所以我们要实现定时器,也要实现两个类MyTimer和MyTimerTask。

        需要准备的内容:

        MyTimer中需要有一个线程不断的扫描schedule进来的代码是否需要执行。

        想要判断谁的时间先到,可以使用优先级队列,让时间先到的位于队首,就能减少需要扫描的数量,节约性能。

        MyTimerTask中,需要能够接收要执行的内容,以及多久后执行,需要实现Comparable接口,才能在优先级队列中进行比较。

        初步实现:

class MyTimerTask implements Comparable<MyTimerTask>{
    //要执行的内容
    private Runnable runnable;
    //时间戳
    private long time;
    //获取要执行的内容和时间
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time - o.time);
    }
    //想外界提供时间
    public long getTime(){
        return time;
    }
    //执行要执行的内容
    public void run(){
        //runnable是传入的要执行的代码,调用Runnable自带的run方法可以启动这个代码的执行
        runnable.run();
    }
}
public class MyTimer {
    //优先级队列,存储要执行的内容
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<MyTimerTask>();
    //schedule方法
    public void schedule(Runnable runnable, long delay){
        //传入要执行的内容和时间
        //将这下内容传递给task
        MyTimerTask task = new MyTimerTask(runnable, delay);
        //将task放入执行队列中
        queue.offer(task);
    }
    //创建线程并不断扫描要执行的内容
    public MyTimer(){
        Thread work = new Thread(() -> {
            //不断进行扫描
            while(true){
                //判断现在队列中是否有要工作的内容
                if(queue.isEmpty()){
                    //执行相应逻辑
                    continue;
                }
                //此时有要执行的内容
                MyTimerTask task = queue.peek();
                //判断是否到达执行时间
                if(System.currentTimeMillis() >= task.getTime()){
                    //时间到,执行代码
                    task.run();
                    //将执行后的任务出队
                    queue.poll();
                }else{
                    //执行时间未到
                    continue;
                }
            }
        });
        work.start();
    }
}

        但是,这个代码在多线程中是线程不安全的.

        queue.offer和queue.peek、poll会对这同一变量queue同时进行读写操作,容易出现问题,所以我们需要加锁.

        有了锁,那么对于队列为空和工作内容时间未到的处理就变简单了:进行wait。

        修改后的代码:

class MyTimerTask implements Comparable<MyTimerTask>{
    //要执行的内容
    private Runnable runnable;
    //时间戳
    private long time;
    //获取要执行的内容和时间
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time - o.time);
    }
    //想外界提供时间
    public long getTime(){
        return time;
    }
    //执行要执行的内容
    public void run(){
        //runnable是传入的要执行的代码,调用Runnable自带的run方法可以启动这个代码的执行
        runnable.run();
    }
}
public class MyTimer {
    //优先级队列,存储要执行的内容
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<MyTimerTask>();
    public Object lock = new Object();
    //schedule方法
    public void schedule(Runnable runnable, long delay){
        synchronized (lock){
            //传入要执行的内容和时间
            //将这下内容传递给task
            MyTimerTask task = new MyTimerTask(runnable, delay);
            //将task放入执行队列中
            queue.offer(task);
            lock.notify();
        }
    }
    //创建线程并不断扫描要执行的内容
    public MyTimer(){
        Thread work = new Thread(() -> {
            //不断进行扫描
            while(true){
                synchronized (lock){
                    //判断现在队列中是否有要工作的内容
                    if (queue.isEmpty()) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    //此时有要执行的内容
                    MyTimerTask task = queue.peek();
                    //判断是否到达执行时间
                    if (System.currentTimeMillis() >= task.getTime()) {
                        //时间到,执行代码
                        task.run();
                        //将执行后的任务出队
                        queue.poll();
                    } else {
                        //等待到达执行时间
                        try {
                            lock.wait(task.getTime() - System.currentTimeMillis());
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        work.start();
    }
}

        

        

         

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值