定时器Timer的使用
在JDK类库中Timer类主要负责计划任务的功能,也就是在指定的时间开始执行某一个任务。此类也常用来做一下周期性同步工作,代替它的有quartz、SpringTask。Timer类的主要作用是设置计划任务,但封装任务的类是TimerTask类(实际该类是一个抽象类,执行任务的代码要放在该类的子类中)。Timer类的主要方法列表如下:
构造方法
成员方法:
Timer内部维护一个 TimerThread 线程,而且线程名字与Thread类一样,默认用一个静态成员变量进行生成全局序列。
public class Timer { private final TaskQueue queue = new TaskQueue(); /** * The timer thread. */ private final TimerThread thread = new TimerThread(queue); /** * This ID is used to generate thread names. */ private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
TimerThread的run方法中while循环获取TaskQueue中的task并且执行任务(mainLoop中执行执行task.run()方法--相当于同步调用)
class TimerThread extends Thread { boolean newTasksMayBeScheduled = true; private TaskQueue queue; TimerThread(TaskQueue queue) { this.queue = queue; } public void run() { try { mainLoop(); } finally { synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // Eliminate obsolete references } } } /** * The main timer loop. (See class comment.) */ private void mainLoop() { while (true) { 。。。。。。 } }
TaskQueue用于存放TimerTask任务(内部维护一个TimerTask数组)。
class TaskQueue { private TimerTask[] queue = new TimerTask[128];
。。。
TimerTask是一个继承Runnable接口的抽象类:
public abstract class TimerTask implements Runnable { final Object lock = new Object();
。。。
TimerTask有一个cancel方法用于将当前任务从任务队列删除。(也就是队列中不会执行该任务,如果正在执行中调用该方法会执行完毕)
public boolean cancel() { synchronized(lock) { boolean result = (state == SCHEDULED); state = CANCELLED; return result; } }
1. Sschedule(task, Date)的用法
此方法用于在指定的时间执行一次task。默认的执行完毕不会结束线程,因为timer的成员属性thread默认是非守护线程,而且其run方法中通过轮询同步调用task的run()方法。
例子1:一个简单的测试:(在未来时间执行一次任务---会到指定之间执行run()方法)
package cn.qlq.thread.fourteen; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo1 extends TimerTask { private static final Logger LOGGER = LoggerFactory.getLogger(Demo1.class); public static void main(String[] args) throws InterruptedException { LOGGER.info("start "); Timer timer = new Timer(); Demo1 task = new Demo1(); Date runtime = new Date(System.currentTimeMillis() + 5000); timer.schedule(task, runtime); LOGGER.info("end "); } @Override public void run() { for (int i = 0; i < 5; i++) { LOGGER.info("threadName -> {},value -> {}", Thread.currentThread().getName(), i + ""); try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
结果:(可以看到先打印的end,后执行的run中方法,并且执行完毕之后进程并未销毁。也验证了是新开一个线程进行操作,并且线程是非守护线程)
查看源码:
public Timer() { this("Timer-" + serialNumber()); } public Timer(String name) { thread.setName(name); thread.start(); }
如果将上面Timer中的TimerThread设为守护线程将不会执行run中方法,因为main线程执行完毕,没有非守护线程的存在,所以守护线程也销毁导致进程销毁。
另一种办法是等待执行完之后调用timer.cancel()
public static void main(String[] args) throws InterruptedException { LOGGER.info("start "); Timer timer = new Timer(); Demo1 task = new Demo1(); Date runtime = new Date(System.currentTimeMillis() + 5000); timer.schedule(task, runtime); LOGGER.info("end "); Thread.sleep(6*1000); timer.cancel(); }
结果:
例子2:多个任务在之前的时间执行---(线性顺序执行多个task,是从queue中获取task然后执行,如果时间早于当前时间会马上执行任务)
package cn.qlq.thread.fourteen; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo1 extends TimerTask { private static final Logger LOGGER = LoggerFactory.getLogger(Demo1.class); private String name; public static void main(String[] args) throws InterruptedException { LOGGER.info("start "); Timer timer = new Timer(); Demo1 task = new Demo1("t1"); Demo1 task2 = new Demo1("t2"); Date runtime = new Date(System.currentTimeMillis() - 5000); timer.schedule(task, runtime); timer.schedule(task2, runtime); LOGGER.info("end "); } @Override public void run() { for (int i = 0; i < 5; i++) { LOGGER.info("threadName -> {},value -> {},taskName->{}", Thread.currentThread().getName(), i + "", name); try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public String getName() { return name; } public void setName(String name) { this.name = name; } public Demo1(String name) { super(); this.name = name; } }
结果:
2. Sschedule(TimerTask task, Date firsttime,long period)的用法
此方法用于在指定的时间执行一次之后任务之后,在指定的period的时间间隔后不停的执行任务
package cn.qlq.thread.fourteen; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo2 extends TimerTask { private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class); private String name; public static void main(String[] args) throws InterruptedException { LOGGER.info("start "); Timer timer = new Timer(); Demo2 task = new Demo2("t1"); Demo2 task2 = new Demo2("t2"); Date runtime = new Date(System.currentTimeMillis() + 5000); timer.schedule(task, runtime, 20 * 1000); timer.schedule(task2, runtime, 20 * 1000); LOGGER.info("end "); } @Override public void run() { for (int i = 0; i < 5; i++) { LOGGER.info("threadName -> {},value -> {},taskName->{}", Thread.currentThread().getName(), i + "", name); try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public String getName() { return name; } public void setName(String name) { this.name = name; } public Demo2(String name) { super(); this.name = name; } }
结果:(时间间隔是从任务开始执行计算的,也就是从当前任务执行的开始时间到下次任务开始时间的间隔是20秒)
3. Shedule(TimerTask task, long delay)的用法
以当前时间为参考,在延迟指定的秒数后执行一次性任务;如果延迟时间是负数会抛出IllegalArgumentException异常。
package cn.qlq.thread.fourteen; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo3 extends TimerTask { private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class); private String name; public static void main(String[] args) throws InterruptedException { LOGGER.info("start "); Timer timer = new Timer(); Demo3 task = new Demo3("t1"); timer.schedule(task, 2 * 1000); LOGGER.info("end "); } @Override public void run() { for (int i = 0; i < 5; i++) { LOGGER.info("threadName -> {},value -> {},taskName->{}", Thread.currentThread().getName(), i + "", name);try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public String getName() { return name; } public void setName(String name) { this.name = name; } public Demo3(String name) { super(); this.name = name; } }
结果:(执行完线程不会关闭)
4. Shedule(TimerTask task, long delay,long period)的用法
以当前时间为参考,在延迟指定的秒数后第一次执行任务;如果延迟时间是负数会抛出IllegalArgumentException异常。并且在period后重复执行任务,执行时间是从上次任务结束时间开始计算。凡是带period的都会在时间间隔后重复执行。
package cn.qlq.thread.fourteen; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo4 extends TimerTask { private static final Logger LOGGER = LoggerFactory.getLogger(Demo4.class); private String name; public static void main(String[] args) throws InterruptedException { LOGGER.info("start "); Timer timer = new Timer(); Demo4 task = new Demo4("t1"); timer.schedule(task, 2 * 1000, 10 * 1000); LOGGER.info("end "); } @Override public void run() { for (int i = 0; i < 5; i++) { LOGGER.info("threadName -> {},value -> {},taskName->{}", Thread.currentThread().getName(), i + "", name); try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public String getName() { return name; } public void setName(String name) { this.name = name; } public Demo4(String name) { super(); this.name = name; } }
结果
5. sheduleAtFixedRate(TimerTask task, Date firstTime,long period)的用法
方法schedule和scheduleAtFixedRate都会按顺序执行,所以不要考虑非线程安全的情况。
在有延时和没有延时的情况下,周期性的任务的下次任务开始时间都是相对于上次任务的开始时间进行延迟(这个在并发编程书中说的是有延迟的情况下相对于结束时间,但是自己测的是相对于开始时间)
schedule和scheduleAtFixedRate的区别在于,如果指定开始执行的时间在当前系统运行时间之前,scheduleAtFixedRate会把已经过去的时间也作为周期执行,而schedule不会把过去的时间算上
1.scheduleAtFixedRate(TimerTask task,long delay,long period) 有延迟,下次任务相对于上次任务的开始时间开始执行:
package cn.qlq.thread.fourteen; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo5 extends TimerTask { private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class); private String name; public static void main(String[] args) throws InterruptedException { LOGGER.info("start "); Timer timer = new Timer(); Demo5 task = new Demo5("t1"); timer.scheduleAtFixedRate(task, 2 * 1000, 10 * 1000); LOGGER.info("end "); } @Override public void run() { for (int i = 0; i < 5; i++) { LOGGER.info("threadName -> {},value -> {},taskName->{}", Thread.currentThread().getName(), i + "", name); try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public String getName() { return name; } public void setName(String name) { this.name = name; } public Demo5(String name) { super(); this.name = name; } }
结果:
2.scheduleAtFixedRate(TimerTask task,Date startTime,long period) 指定开始执行的时间在当前系统运行时间之前,会把已经过去的时间也作为周期执行
package cn.qlq.thread.fourteen; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo6 extends TimerTask { private static final Logger LOGGER = LoggerFactory.getLogger(Demo6.class); private String name; public static void main(String[] args) throws InterruptedException { LOGGER.info("start "); Timer timer = new Timer(); Demo6 task = new Demo6("t1"); timer.scheduleAtFixedRate(task, new Date(System.currentTimeMillis() - 5000), 10 * 1000); LOGGER.info("end "); } @Override public void run() { for (int i = 0; i < 3; i++) { LOGGER.info("threadName -> {},value -> {},taskName->{}", Thread.currentThread().getName(), i + "", name); try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public String getName() { return name; } public void setName(String name) { this.name = name; } public Demo6(String name) { super(); this.name = name; } }
结果:(第二次任务把之前的5秒钟也包含在延迟时间内)
补充:
1.TimerTask有一个cancel方法用于将当前任务从任务队列删除。(也就是队列中不会执行该任务,如果正在执行中调用该方法会执行完毕)
package cn.qlq.thread.fourteen; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo2 extends TimerTask { private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class); private String name; public static void main(String[] args) throws InterruptedException { LOGGER.info("start "); Timer timer = new Timer(); Demo2 task = new Demo2("t1"); Demo2 task2 = new Demo2("t2"); Date runtime = new Date(System.currentTimeMillis() + 5000); timer.schedule(task, runtime, 20 * 1000); timer.schedule(task2, runtime, 20 * 1000); LOGGER.info("end "); } @Override public void run() { for (int i = 0; i < 5; i++) { LOGGER.info("threadName -> {},value -> {},taskName->{}", Thread.currentThread().getName(), i + "", name); if (name.equals("t1")) { this.cancel(); } try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public String getName() { return name; } public void setName(String name) { this.name = name; } public Demo2(String name) { super(); this.name = name; } }
结果:(t1任务被删除)
2.Timer的cancel方法用于清空所有任务队列(如果有任务正在执行会等任务执行完清空;有时候不一定会停掉,因为cancel方法不一定能抢到queue对象的锁)
package cn.qlq.thread.fourteen; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo2 extends TimerTask { private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class); private String name; public static void main(String[] args) throws InterruptedException { LOGGER.info("start "); Timer timer = new Timer(); Demo2 task = new Demo2("t1"); Demo2 task2 = new Demo2("t2"); Date runtime = new Date(System.currentTimeMillis()); timer.schedule(task, runtime, 20 * 1000); timer.schedule(task2, runtime, 20 * 1000); LOGGER.info("end "); Thread.sleep(2 * 1000); timer.cancel(); LOGGER.info("timer cancel"); } @Override public void run() { for (int i = 0; i < 5; i++) { LOGGER.info("threadName -> {},value -> {},taskName->{}", Thread.currentThread().getName(), i + "", name); if (name.equals("t1")) { this.cancel(); } try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public String getName() { return name; } public void setName(String name) { this.name = name; } public Demo2(String name) { super(); this.name = name; } }
结果: