一,定时器
1.定时器的概念
定时器是Java开发中一个重要的组件(功能类似于闹钟),可以指定一个任务在多长时间后执行(尤其在网络编程的时候,如果网络卡顿很长时间没有响应用户的需求,此时可以使用定时器来终止用户的请求),所以一个定时器最少具有两个功能:
一个需要执行的任务
指定的时间去执行任务
在Java标准中提供了Timer类来封装定时器这样的功能。
2.Timer类
Timer类是Java标准库提供的内置类,其核心方法是schedule方法;
Timer类构造方法
Timer() | 创建一个新的计时器 |
Time(boolean isDaemon) | 创建一个新的定时器,其相关线程可以指定为 run as a deamon |
Time(String name) | 创建一个新的定时器,其相关线程具有指定的名称 |
Time(String name, boolean isDaemon) | 创建一个新的定时器,其相关线程具有指定的名称,可以指定为 run as a deamo |
Timer类的构造方法一共有4种,一般最常用的就是第一种无参的构造方法
schedule方法
schedule方法是Timer类的核心,定时器所执行的操作都是由该方法提供,所以我们来看一下schedule方法的源码:
该方法是一个普通方法,在使用前需要先创建Timer类对象
该方法有两个参数:第一个是TimerTask task(重写内部的run方法来指定定时器的任务),第二个是long delay(描述的是多长时间后执行该任务,单位是ms)
其中这里的TimerTask类似于之前创建的线程中的Runnable,都是指定一个具体的任务
定时器任务示例
/*** 创建一个定时器任务* 任务内容:输出hello world* 时间:1000ms之后执行*/
import java.util.Timer;
import java.util.TimerTask;public class ThreadDemo2 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello world");}},1000);}
}
执行结果:
程序将会在1000ms毫秒之后打印“hello word”,但是此时进程并没有结束,因为定时器创建出来的线程默认属于前台线程,前台线程会阻止进程的结束。
二,模拟实现简单的定时器
定时器需要满足两个功能:
1.让被注册的任务能够在指定的时间被执行
2.一个定时器可以注册N个任务,N个任务按照最初约定的时间,按顺序执行
解决办法:
对于第一个功能,我们只需要单独在定时器内部搞个线程,让这个线程周期性的扫描,判定任务是否到时间了,如果到时间了就执行,否则就不执行
对于第二个问题,我们需要一个数据结构来保存这些任务,因为任务需要按照时间的先后顺序来执行,所以这里采用优先级队列来保存任务(同时这个优先级队列需要按照指定的优先级来保存),又因为在多线程的环境下使用定时器,所以优先级队列需要带有阻塞功能,即采用PriorityBlockingQueue这样的数据结构。
代码如下:
package ThreadLearning;import java.util.concurrent.PriorityBlockingQueue;/*** 使用一个类来表示一个定时器中的任务*/
class MyTask implements Comparable<MyTask> {//要执行的任务private Runnable runnable;//任务啥时候执行(使用毫秒时间戳表示)private long time;public MyTask(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}//获取当前任务的时间public long getTime() {return time;}//执行时间public void run() {runnable.run();}//重写compareTo方法来指定优先级队列的优先级@Overridepublic int compareTo(MyTask o) {return (int) (this.time - o.time);}
}class MyTimer {//扫描线程private Thread t = null;//阻塞队列用来保存任务//传入的参数是MyTask这个类private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();public MyTimer() {t = new Thread(() -> {while (true) {try {synchronized (this) {MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (curTime < myTask.getTime()) {//还没到点,先不执行queue.put(myTask);//设置wait的等待时间,即队首元素与现在时间的差值this.wait(myTask.getTime() - curTime);} else {//时间到了,执行任务myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}/*** 该方法本身很简单,只是单纯把任务放到队列中,指定两个参数** @param runnable 任务内容* @param after 任务在多少毫秒之后执行*/public void schedule(Runnable runnable, long after) {//注意这里时间上的换算MyTask myTask = new MyTask(runnable, System.currentTimeMillis() + after);queue.put(myTask);//只要有新的线程加入就会唤醒waitsynchronized (this) {this.notify();}}
}
测试:
/*** 分别创建三个任务,三个任务的执行时间分别是1000 2000 3000* 如果打印结果分别按照顺序执行则正确*/
public class ThreadTest {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务1");}}, 1000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务2");}}, 2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务3");}}, 3000);}
}
总结:
这里会按照设定时间的先后打印任务,发现此时打印完三个任务之后程序没有停止,因为在刚刚模拟实现的定时器中采用了while(true)的操作,会一直循环从阻塞队列中获取任务,队列为空就会发生阻塞等待(标准库中的定时器也是想相同的逻辑),默认为前台线程;
因为采用了优先级队列的数据结构,所以对自己定义的MyTask类一定要设置优先级,需要实现Compareable接口重写compareTo方法;
这里一定要用wait方法设置一个时间爱参数避免“死等”的现象,从而浪费CPU资源
在schedule方法中设置notify方法来唤醒,每次注册一个任务之后就进行判读时间是否到了是否需要执行