多线程案例之定时器

什么是定时器

定时器, 就类似于一个闹钟, 当设定的时间一到, 就执行某个指定的代码.

标准库中的定时器

Java 标准库中提供了一个 Timer 类, 其核心方法为 schedule.

方法作用
void schedule(TimerTask task, long delay)
到达指定的 delay (单位毫秒)时间后, 执行指定任务 task

schedule 有两个参数, 第一个参数指定要执行的代码, 第二个参数指定时间, 时间单位为毫秒

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

定义一个 timer 可以添加多个任务, 当这个代码执行完, 发现进程并没结束, 这是因为 Timer 里内置了线程(前台线程), 它不知道你的代码是否还会添加新的任务进来, 有一个主动结束的方法 cancle() 

实现定时器

首先需要有一个线程, 负责帮我们掐时间, 等时间到达, 这个线程就帮我们执行.

还需要一个队列/数组, 负责保存所有 schedule 进来的任务.

我们使用优先级队列来实现上述内容. 每个任务都带有时间, 肯定先执行时间小的, 再执行时间大的.

可以使用标准库中提供的 PriorityQueue (线程不安全) -----> 我们使用这个, 手动加锁.

标准库中也提供了 PriorityBlockingQueue (线程安全)  -----> 这个不好控制, 容易出问题~

创建 MyTimerTask 类, 来描述一个任务

class MyTimerTask implements Comparable<MyTimerTask>{
    //在什么时间点来执行任务
    private long time;
    //实际任务要执行的代码
    private Runnable runnable;
    //构造方法
    //这里的 delay 是一个相对时间间隔
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //计算要执行任务的绝对时间
        this.time = System.currentTimeMillis() + delay;
    }
    public void run() {
        runnable.run();
    }
    public long getTime() {
        return time;
    }
    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
    }
}

因为这些任务是要放到优先级队列里的, 所以应该是可比较的, 这里要实现 Comparable 接口, 重写compareTo() 方法, 至于谁减谁, 试一试就知道了.

创建 MyTimer 类, 表示一个定时器

class MyTimer {
    //负责扫描任务队列, 执行任务的线程
    private Thread t = null;
    //任务队列
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    public void schedule (Runnable runnable, long delay) {
        MyTimerTask task = new MyTimerTask(runnable, delay);
        queue.offer(task);
    }
    //构造方法, 创建扫描线程, 让扫描线程来判定和执行
    public MyTimer() {
        
        t = new Thread(()->{
           //扫描线程需要反复扫描队首元素, 并判断队首元素时间是不是到了
            //如果时间没到, 啥也不干
            //如果时间到了, 执行任务, 并删除它
            while(true) {
                if(queue.isEmpty()){
                    //先不做处理
                    continue;
                }
                MyTimerTask task = queue.peek();
                //获取当前时间
                long curTime = System.currentTimeMillis();
                if(curTime >= task.getTime()) {
                    //时间到了, 执行并删除它
                    task.run();
                    queue.poll();
                } else {
                    //时间没到,暂时先不执行
                    continue;
                }
            }
        });
    }

}

加锁 

首先创建一个锁对象

    public Object locker = new Object();

 引入锁的目的 是保证队列在多线程下是安全的, 所以把有关队列的操作都要加锁.

然后就是阻塞等待了, 当队列为空, 需要阻塞等待, 那什么时候唤醒呢?

肯定是队列不为空啦~ 

 当判断任务时间还没有到达, 也是需要等待的, 不过这里可不是盲等, 要有最大等待时间.

这个时间怎么算呢? 就是用该任务设定的绝对时间减去当前时间就可以啦~

locker.wait(task.getTime() - curTime);

要注意, 这里不能用 sleep .

如果后续有任务进来, 但又没有解锁, 那就会错过这个任务. 而且一直不解锁, 占用资源.

完整代码:

//描述一个任务
class MyTimerTask implements Comparable<MyTimerTask>{
    //在什么时间点来执行任务
    private long time;
    //实际任务要执行的代码
    private Runnable runnable;
    //构造方法
    //这里的 delay 是一个相对时间间隔
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //计算要执行任务的绝对时间
        this.time = System.currentTimeMillis() + delay;
    }
    public void run() {
        runnable.run();
    }
    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
    }
}

class MyTimer {
    //负责扫描任务队列, 执行任务的线程
    private Thread t = null;
    public Object locker = new Object();
    //任务队列
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    public void schedule (Runnable runnable, long delay) {
        synchronized (locker){
            MyTimerTask task = new MyTimerTask(runnable, delay);
            queue.offer(task);
            //添加元素之后, 队列不为空, 唤醒
            locker.notify();
        }
    }

    //构造方法, 创建扫描线程, 让扫描线程来判定和执行
    public MyTimer() {
        t = new Thread(()->{
           //扫描线程需要反复扫描队首元素, 并判断队首元素时间是不是到了
            //如果时间没到, 啥也不干
            //如果时间到了, 执行任务, 并删除它
            while(true) {
                synchronized (locker) {
                    try {
                        while (queue.isEmpty()) {
                            //队列为空, 等待
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        //获取当前时间
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.getTime()) {
                            //时间到了, 执行并删除它
                            task.run();
                            queue.poll();
                        } else {
                            //未到达指定时间
                            locker.wait(task.getTime() - curTime);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        //不要忘记!!
        t.start();
    }

}



public class ThreadDemo1 {
    public static void main(String[] args) {
        MyTimer time = new MyTimer();
        time.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 1");
            }
        },3000);
        time.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 2");
            }
        },5000);
    }
}

 

到这里就实现完成啦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值