赞
踩
在日常生活中,如果我们想要在 t 时间 后去做一件重要的事情,那么为了防止忘记,我们就可以使用闹钟的计时器功能,它会在 t 时间后执行任务(响铃)提醒我们去执行这件事情. — 这就是Java定时器的简单功能。它作为一种日常开发组件。约定一个时间,时间到达之后,执行某个任务。常被用于网络通信。
也比如在客户端和服务器之间,当客户端发出去请求之后,服务器就要返回响应,客户端这边要等待响应,而网络环境是复杂的,如果等待时间较长,这个原因是啥,是请求没法送过去?响应丢了?还是服务器出问题了。对于客户端来说,不能无限的等,需要先设置一个最大的期限。这时"等待最大期限"就可以通过定时器的方式实现了。
Java中的定时器的类是 : Timer ,为util包中的一个无继承关系的类, 从该类的构造方法中,我们可以使用无参构造器创建该类的对象,也可以在创建类对象的时候指定定时器中所需要的线程的名字,与是否为守护进程。
- 在定时器中最常用的方法就是 schedule(TimerTask task, long delay)
- 该方法传参的是一个 TimerTask 对象,与定时器约定的执行时间间隔 delay
- 包:import java.util.Timer;
而 TimerTask 类则是一个来描述计时器任务的类,该类中有 抽象方法 run(),并且该类是实现了runnable接口的,所以我们给 schedule 传参中的 TimerTask 对象都要重写 run() 方法,重写的run() 方法中的语句,则是定时器需要执行的语句。
而 delay 则是我们约定从当前时间后的 delay 内执行传入的任务.时间单位为 毫秒。
接下来我们来看一个简单的定时器的使用 :
我们在创建定时器的时候指定了定时器中的扫描线程的线程名,然后使用 schedule 方法传入任务与任务执行的间隔时间 1000 毫秒
这个时候在执行该代码的1000毫秒后,定时器就会将该任务执行。
- import java.util.Timer;
- import java.util.TimerTask;
-
- public class demo1 {
- public static void main(String[] args) {
- Timer timer = new Timer("线程1");
- timer.schedule(new TimerTask() { //使用匿名内部类继承TImerTask类
- @Override
- public void run() { //TimerTask类实现了Runnable接口要重写run方法
- System.out.println("执行任务");
- }
- },1000); //delay相对时间 任务执行时间
-
- System.out.println("程序启动!");
- }
- }
主线程执行schedule方法的时候,就是把这个任务放到timer对象中了。并且timer里面也包含一个线程(扫描线程),时间一到,扫描线程就会执行刚才安排的任务了。
可以发现,程序运行完,整个程序并没有结束。正是因为TImer里的线程,阻止了线程结束!
利用 jconsole 观察该线程处于 WAITING 状态:
- //描述一个任务的类
- public 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;
- }
-
- //为外部提供获取任务发生时间
- public long getTaskTime() {
- return this.time;
- }
-
- //为外部提供获取任务
- public Runnable getRunnable() {
- return this.runnable;
- }
-
- //重写比较方法
- @Override
- public int compareTo(MyTimerTask o) {
- return (int)(this.time-o.time);
- }
- }
实现Comparable接口是因为数据结构我们用到了优先级队列,需要重写比较方法,重新定义比较规则。有两种方法,一种是实现Comparable接口,另一种是比较器Comparator接口,用内部类实现。
- import java.util.PriorityQueue;
-
- //定时器 即指定几分钟后或其他时间后干什么
- //定时器
- public class MyTimer {
-
- //优先级队列 使用比较器 匿名内部类
- //private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {
- // @Override
- // public int compare(MyTimerTask o1, MyTimerTask o2) {
- // return (int) (o1.getTaskTime()-o2.getTaskTime());
- // }
- //});
-
- //优先级队列
- private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
- //多个线程针对同一个对象上锁 锁对象
- private Object locker = new Object();
- public void schedule(Runnable runnable,long delay) {
- //线程不安全
- synchronized (locker) {
- //添加任务及任务时间
- queue.offer(new MyTimerTask(runnable,delay));
- //唤醒队列
- locker.notify();
- }
- }
-
- //扫描线程
- public MyTimer() {
- //创建一个扫描线程
- Thread t1 = new Thread(()->{
- //不停地扫描队列 即队头 查看是否到达时间
- //有可能下一次新添加的任务的时间更短 队头改变
- while(true) {
- synchronized (locker) {
- while (queue.isEmpty()) {
- try {
- //队列为空 等待
- locker.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- //队列不为空 有任务 下面进行时间比较
-
- //获取当前任务
- MyTimerTask task = queue.peek();
-
- //获取当前时间 时间戳
- long currTime = System.currentTimeMillis();
-
- if(currTime>=task.getTaskTime()) {
- //到任务时间 执行任务
- task.getRunnable().run();
- queue.poll();
- }else{
- //未到任务时间 也进行等待 降低扫描速度
- try {
- locker.wait(task.getTaskTime()-currTime);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- }
- }
- });
-
- //启动线程
- t1.start();
- }
-
- }
数据结构我们选择使用优先级队列,有啥好处?假如在选择数据结构之前,我们先假设使用数组ArrayList,此时扫描线程,就需要不停地遍历数组中的每个任务,判定每个任务是否到达执行时间。这样的遍历效率是非常低的。如果使用优先级队列,再重写比较方法,让整个任务由时间大小按照小根堆排列,那么最先执行的就是时间最小的任务了,时间复杂度将降到O(1),判定任务时间是否到达更高效。
在该类的构造方法中,我们还创建了一个线程,不断地对任务队列中优先级最高(最快执行)的任务进行查看, 看是否到达执行时间。
当队列为空时,线程进入阻塞等待,直到添加一个任务时,线程继续执行。
队列中有任务时,但当前时间最短的任务还未到达执行时间时,也进行阻塞等待。这里的阻塞等待是有参的,为执行时间与当前时间的差值。其实也完全可以不等待,继续循环扫描。此处阻塞的好处就是wait之后,就会释放锁,线程就不会在CPU上执行了,就可以把CPU资源让给其他线程使用了。
那么对于wait和sleep来说都是等待,为啥不用sleep?sleep是指定时间让线程进行休眠,假如在sleep的过程中,我添加了一个比之前队列中任务执行时间还早的任务,那么sleep就不能及时执行最新的这个任务。而我设计的代码中,若使用wait,每添加一个任务时,notify都会唤醒不管是因为空队列进入阻塞状态的线程,或者是因为未到达任务时间而阻塞等待的线程(两种阻塞不会同时出现),就算是添加了一个比之前队列中任务执行时间还早的任务,也能及时执行任务。
- public class test {
- public static void main(String[] args) {
- MyTimer timer = new MyTimer();
- timer.schedule(new Runnable() {
- @Override
- public void run() {
- System.out.println("1000");
- }
- },1000);
- //System.out.println("00");
-
- timer.schedule(new Runnable() {
- @Override
- public void run() {
- System.out.println("2000");
- }
- },2000);
-
-
- timer.schedule(new Runnable() {
- @Override
- public void run() {
- System.out.println("3000");
- }
- },3000);
- }
- }
结果:
三个注意点:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。