案例1:定时器打印Hello World!
import java.text.ParseException; import java.util.Date; import java.util.Timer; import java.util.TimerTask; /** * @author :jiaolian * @date :Created in 2021-01-05 20:42 * @description:Timer启动后内置线程不销毁 * @modified By: * 公众号:叫练 */ public class TimerThreadNoStopTest { //TimerTask为抽象类,继承TimerTask类必须要实现里面抽象方法 private static class Task extends TimerTask { @Override public void run() { System.out.println("hello world!"); } } public static void main(String[] args) throws ParseException { Timer timer = new Timer(); Task task = new Task(); long currenTime = System.currentTimeMillis(); //提交Task线程;程序按传入日期运行 timer.schedule(task,new Date(currenTime)); //【注意】用完定时器后,要及时取消它 //timer.cancel(); //timer.purge(); } }
如上面程序代码,Timer提交了一个task任务并传入了currenTime当前时间,控制台马上打印了"hello world!",如果schedule传入的第二个参数是new Date(currenTime+2000)表示延迟2m执行task任务,这里简单使用方法就不过多的描述了。下图是程序控制台打印结果,如果大家执行了你会发现一个问题,程序一直不结束运行,也就是程序不死。那是什么导致这样的结果呢?
线程不死的原因?
原因分析:如下图所示,主线程执行Timer timer = new Timer();会创建了一个新的子线程timer,timer线程通过死循环来取队列里面的任务task[1],队列其实就是一个数组实现TaskQueue,队列里面如果没有任务,那timer线程就会一直等待直到主线程调用schedule提交任务,主线程就会将task加入到TaskQueue队列数组并通知timer线程执行任务并删除队列的第一个任务,如果是主线程提交的是定时任务,就会将任务重新加入队列,任务执行完毕后,如果此时队列为空,timer线程就会继续等待任务提交到队列,一直会循环上面的过程。如果想退出timer线程,可以调用cancel方法会退出死循环。线程不死原因是timer线程一直在等待主线程提交任务,timer线程和主线程通信是通过调用wait/notify实现。我们之前做生产者/消费者案例时详细介绍过,这里老铁不再重复叙述了,可以去翻看文章**《母鸡下蛋实例》**。这个过程中,我们发现timer是一个单线程,我是单线程怎么了?单线程也有错吗?思考个问题,如果timer这个单线程提交了两个任务怎么办?我们看下面代码!
案例2:单线程问题
import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; /** * @author :jiaolian * @date :Created in 2021-01-06 10:53 * @description:多任务执行测试,任务只能顺序执行; * @modified By: * 公众号:叫练 */ public class MultTaskExecuteTest { private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static class MyTask1 extends TimerTask { @Override public void run() { System.out.println("task1 begin:"+SIMPLE_DATE_FORMAT.format(new Date())); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("task1 end:"+SIMPLE_DATE_FORMAT.format(new Date())); } } private static class MyTask2 extends TimerTask { @Override public void run() { System.out.println("task2 begin:"+SIMPLE_DATE_FORMAT.format(new Date())); System.out.println("task2 end:"+SIMPLE_DATE_FORMAT.format(new Date())); } } public static void main(String[] args){ Timer timer = new Timer(); MyTask1 myTask1 = new MyTask1(); MyTask2 myTask2 = new MyTask2(); long curTime = System.currentTimeMillis(); System.out.println("当前时间:"+SIMPLE_DATE_FORMAT.format(curTime)); timer.schedule(myTask1,new Date(curTime)); //myTask1执行时间过长,myTask2 被执行时间会被延迟; timer.schedule(myTask2,new Date(curTime+1000)); } }
如上面程序代码,timer线程提交了两个任务myTask1,myTask2,myTask1任务会立刻执行,myTask2计划延迟一秒执行,myTask1执行过程中会休息10秒钟,我们观察任务执行时间如下图所示,myTask2任务是等待myTask1任务执行完毕后再执行的,其实myTask2只是延迟一秒执行,结果却延迟了10秒,说明了timer单线程会串行化任务导致myTask2延迟执行,所以**Timer是适合轻量级定时任务,如果设置大量任务,可能会存在延迟执行情况。
定时器实际应用场景
在日常系统开发中,相信你遇到过类似需要重复执行的任务,比如每天凌晨2点清理数据库某张表的垃圾数据,页面显示设备(服务器)运行状态也需要每隔3秒调用设备状态接口查询设备情况等,这些功能开发都需要用到定时器(Timer),当然Timer定时器也有自身的缺陷,比如它是单线程的,后面会说到线程池中的定时器是多线程的,可以优化Timer,所以掌握Timer定时器为后面学习高阶内容打好基础。知其然知其所以然,在实际应用中我们能得心应手!