JAVA定时任务 Timer TimerTask 笔记
前言
我并不喜欢干什么都动不动弄一大坨框架, 首先还是该掌握好自带的基础工具 , 然后是spring , 然后才是更大坨的工具
Java最基本的定时任务 : new一个线程并指定间隔和结束条件
public static boolean 是否循环 = true;
public static long 间隔时间毫秒 = 3000;
public static void 干点什么方法() { System.out.println("世界你好");}
public static void main(String...arguments) {
new Thread(()->{
while(是否循环) {
干点什么方法();
try{ Thread.sleep(间隔时间毫秒); }catch(Exception e) {System.err.println("sleep能有什么错");}
}
}).start();
}
Timer + TimerTask 简单用法说明
例子:
- 实现 TimerTask的run方法, 明确要做什么
可以继承实现, 也可用匿名内部类 - new 一个Timer
- 调用Timer实例的 schedule 或 scheduleAtFixedRate 方法
将TimerTask放入Timer,并指定开始时间 和 间隔时间
延时三秒后执行一次:
long 三秒后=3000;
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("要执行的代码");
timer.cancel();
}
}, 三秒后);
延时三秒后, 每秒执行一次, 用scheduleAtFixedRate方法:
long 三秒后=3000 , 间隔1秒=1000;
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("要执行的代码");
}
}, 三秒后, 间隔1秒);
延时三秒后, 每秒执行一次, 用schedule方法:
long 三秒后=3000 , 间隔1秒=1000;
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("要执行的代码");
try {Thread.sleep(500);}catch(Exception e) {} //不会多加500毫秒
}
}, 三秒后, 间隔1秒);
java.util.Timer
基本原理
创建了一个任务队列 , 创建了一个执行线程
来自openJDK15的源码:
public class Timer {
/**
* The timer task queue. This data structure is shared with the timer
* thread. The timer produces tasks, via its various schedule calls,
* and the timer thread consumes, executing timer tasks as appropriate,
* and removing them from the queue when they're obsolete.
*/
private final TaskQueue queue = new TaskQueue();
/**
* The timer thread.
*/
private final TimerThread thread = new TimerThread(queue);
Timer会创建一个线程执行TaskQueue中的TimerTask
一个Timer一个线程 , 所以 , 如果想多个不相干的任务互不干扰,就多new几个Timer,分别放入
相干的任务可以放入同一个Timer
主要方法
构造方法
- public Timer()
- public Timer(boolean isDaemon)
- public Timer(String name)
- public Timer(String name, boolean isDaemon)
Timer()
public Timer() {
this("Timer-" + serialNumber());
}
Timer(String name)
public Timer(String name) {
thread.setName(name);
thread.start();
}
Timer(boolean isDaemon)
public Timer(boolean isDaemon) {
this("Timer-" + serialNumber(), isDaemon);
}
Timer(String name, boolean isDaemon)
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
构造方法的 isDaemon 参数
isDaemon 表示 是否是守护线程 , 默认设置参考 Thread类
/* Whether or not the thread is a daemon thread. */
private boolean daemon = false;
不指定的话, 默认false 不是守护线程
守护线程和默认线程的区别
- 守护线程更像是跟班 , 主线程退出,就跟着退出了. 所以, 守护timer可能什么也没做程序就完了
- 默认线程会一直等到自身执行完, 或一直在循环中, 所以, 默认timer排程后, 即便自己已执行完, 主线程也早执行完了, 但timer的线程依旧不会退出
schedule 和 scheduleAtFixedRate
schedule
- public void schedule(TimerTask task, long delay) //延时执行一次
- public void schedule(TimerTask task, Date time) //延时执行一次
- public void schedule(TimerTask task, long delay, long period) //延时且循环
- public void schedule(TimerTask task, Date firstTime, long period) //延时且循环
scheduleAtFixedRate
- public void scheduleAtFixedRate(TimerTask task, long delay, long period) //延时且循环
- public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) //延时且循环
比较 schedule方法 和 scheduleAtFixedRate方法 的区别
- schedule(TimerTask task, long delay, long period) openJDK15
public void schedule(TimerTask task, long delay, long period) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
sched(task, System.currentTimeMillis()+delay, -period);
}
- scheduleAtFixedRate(TimerTask task, long delay, long period) openJDK15
public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
sched(task, System.currentTimeMillis()+delay, period);
}
它们都调用sched方法
不同在于, 传入的 period 一个为负 , 一个为正
- private void sched(TimerTask task, long time, long period)
/**
* Schedule the specified timer task for execution at the specified
* time with the specified period, in milliseconds. If period is
* positive, the task is scheduled for repeated execution; if period is
* zero, the task is scheduled for one-time execution. Time is specified
* in Date.getTime() format. This method checks timer state, task state,
* and initial execution time, but not period.
*
Schedule(排程)指定的timerTask以指定的时间在指定的period(以毫秒为单位)执行。
如果period为正,则计划将任务安排为重复执行;
如果period为零,则task只执行一次。
时间以Date.getTime()格式指定。
此方法检查timer状态,task状态和初始执行时间,但不检查period(周期)。
*/
private void sched(TimerTask task, long time, long period) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time.");
// Constrain value of period sufficiently to prevent numeric
// overflow while still being effectively infinitely large.
if (Math.abs(period) > (Long.MAX_VALUE >> 1))
period >>= 1;
synchronized(queue) {
if (!thread.newTasksMayBeScheduled)
throw new IllegalStateException("Timer already cancelled.");
synchronized(task.lock) {
if (task.state != TimerTask.VIRGIN)
throw new IllegalStateException(
"Task already scheduled or cancelled");
task.nextExecutionTime = time;
task.period = period;
task.state = TimerTask.SCHEDULED;
}
queue.add(task);
if (queue.getMin() == task)
queue.notify();
}
}
sched源码👆 仍然看不出它们的区别, 但可以知道每个task都有period属性, 在sched中被传入 " task.period = period; "
继续查找, 终于找到干循环工作的主方法
class TimerThread类 的 private void mainLoop()
- TimerThread类 的 private void mainLoop()
/**
* The main timer loop. (See class comment.)
*/
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// Wait for queue to become non-empty
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
if (queue.isEmpty())
break; // Queue is empty and will forever remain; die
// Queue nonempty; look at first evt and do the right thing
long currentTime, executionTime;
task = queue.getMin();
synchronized(task.lock) {
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue; // No action required, poll queue again
}
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
if (taskFired = (executionTime<=currentTime)) {
if (task.period == 0) { // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime);
}
if (taskFired) // Task fired; run it, holding no locks
task.run();
} catch(InterruptedException e) {
}
}
}
可以看到这句
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
和这句
queue.wait(executionTime - currentTime); //暂停(执行时间减去当前时间)
- 使用 schedule 时 , rescheduleMin方法 传入 currentTime -period(加) (当前时间加上周期)(因为period被负过,所以是加,我猜是编程大神们故意绕的)
- 使用 scheduleAtFixedRate 时 , rescheduleMin方法 传入 executionTime+period (执行时间加上周期) (提前透露, 就是传给队列中第一个任务的nextExecutionTime)
追查 rescheduleMin 方法 它属于 class TaskQueue类
/**
* Sets the nextExecutionTime associated with the head task to the
* specified value, and adjusts priority queue accordingly.
* 将与头任务关联的nextExecutionTime设置为指定的值,并相应地调整优先级队列。
*/
void rescheduleMin(long newTime) {
queue[1].nextExecutionTime = newTime;
fixDown(1);
}
追查 TaskQueue类 的 fixDown 方法
/**
* Establishes the heap invariant (described above) in the subtree
* rooted at k, which is assumed to satisfy the heap invariant except
* possibly for node k itself (which may have a nextExecutionTime greater
* than its children's).
*
* This method functions by "demoting" queue[k] down the hierarchy
* (by swapping it with its smaller child) repeatedly until queue[k]'s
* nextExecutionTime is less than or equal to those of its children.
*
* 在以k为根的子树中建立堆不变式(如上所述),假定它满足节点不变的堆不变式(节点k本身的nextExecutionTime可能大于子节点的不变性)。
* 此方法通过向下“降级” queue [k]起作用。 重复层次结构(通过与较小的子级交换),直到queue [k]的nextExecutionTime小于或等于其子级。
*/
private void fixDown(int k) {
int j;
while ((j = k << 1) <= size && j > 0) {
if (j < size &&
queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
j++; // j indexes smallest kid
if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
break;
TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
k = j;
}
}
再查 class TaskQueue 的源码 (部分)
private TimerTask[] queue = new TimerTask[128];
void add(TimerTask task) {
// Grow backing store if necessary
if (size + 1 == queue.length)
queue = Arrays.copyOf(queue, 2*queue.length);
queue[++size] = task;
fixUp(size);
}
TimerTask getMin() {
return queue[1];
}
可以知道, TaskQueue类 保存一个TimerTask[] 数组, 由[1]开始, [0]为空
rescheduleMin方法设置TimerTask[1]的nextExecutionTime (下一次执行时间)并和TimerTask[2]交换位置(如果有的话)
结论:
Timer schedule 和 scheduleAtFixedRate 方法的区别
在传入的TimerTask数量大于1时才有
schedule 的下次执行时间是 当前系统时间+周期(period)
scheduleAtFixedRate 的下次执行时间是 上次执行时间+周期(period)
Timer的schedule和scheduleAtFixedRate单任务区别测试
public class Timer的schedule和scheduleAtFixedRate单任务区别测试{
static long 延时=5000, 休眠1秒 = 1000 , 间隔2秒 = 2000;
static class TimerTask扩展1 extends TimerTask{
String name = this.getClass().getSimpleName();
@Override
public void run() {
System.out.println("TimerTask名字 : "+name);
System.out.append(name).append(".scheduledExecutionTime()=").println(this.scheduledExecutionTime());
System.out.append(name).append(" 执行开始的时间点 : ").println(new Timestamp(System.currentTimeMillis()));
try {Thread.sleep(1000);}catch(Exception e) {}
System.out.append(name).append(" 执行完成的时间点 : ").println(new Timestamp(System.currentTimeMillis()));
}
}
static class 放入schedule执行的TimerTask类 extends TimerTask扩展1{}
static class 放入scheduleAtFixedRate执行的TimerTask类 extends TimerTask扩展1{}
static Timer 使用schedule的Timer = new Timer();
static Timer 使用scheduleAtFixedRate的Timer = new Timer();
static 放入schedule执行的TimerTask类 放入schedule执行的TimerTask实例 = new 放入schedule执行的TimerTask类();
static 放入scheduleAtFixedRate执行的TimerTask类 放入scheduleAtFixedRate执行的TimerTask实例 = new 放入scheduleAtFixedRate执行的TimerTask类();
public static void main(String...arugments) {
使用scheduleAtFixedRate的Timer.scheduleAtFixedRate(放入scheduleAtFixedRate执行的TimerTask实例, 延时, 间隔2秒);
使用schedule的Timer.schedule(放入schedule执行的TimerTask实例, 延时, 间隔2秒);
}
}
测试结果 , 单任务下 间隔是一样的
cancel 方法 用于退出
Timer 和 TimerTask 都有 cancel方法Timer 的 cancel 源码
/**
* Terminates this timer, discarding any currently scheduled tasks.
* Does not interfere with a currently executing task (if it exists).
* Once a timer has been terminated, its execution thread terminates
* gracefully, and no more tasks may be scheduled on it.
*
* <p>Note that calling this method from within the run method of a
* timer task that was invoked by this timer absolutely guarantees that
* the ongoing task execution is the last task execution that will ever
* be performed by this timer.
*
* <p>This method may be called repeatedly; the second and subsequent
* calls have no effect.
*
* 终止此计时器,丢弃任何当前计划的任务。不干扰当前正在执行的任务(如果存在)。一旦计时器终止,其执行线程就会正常终止,并且无法在其上安排更多任务。
请注意,从此计时器调用的计时器任务的run方法中调用此方法绝对可以保证正在进行的任务执行是该计时器永远执行的最后一个任务执行。
可以重复调用此方法。 反正第二次及随后的调用均无效。
*/
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
purge方法
purge方法用于清除Queue中state=CANCELLED的TimerTask
/**
* Removes all cancelled tasks from this timer's task queue. <i>Calling
* this method has no effect on the behavior of the timer</i>, but
* eliminates the references to the cancelled tasks from the queue.
* If there are no external references to these tasks, they become
* eligible for garbage collection.
*
* <p>Most programs will have no need to call this method.
* It is designed for use by the rare application that cancels a large
* number of tasks. Calling this method trades time for space: the
* runtime of the method may be proportional to n + c log n, where n
* is the number of tasks in the queue and c is the number of cancelled
* tasks.
*
* <p>Note that it is permissible to call this method from within
* a task scheduled on this timer.
*
* 从此计时器的任务队列中删除所有已取消的任务。 调用此方法对计时器的行为没有影响,但是会从队列中删除对已取消任务的引用。如果没有外部引用,这些任务将有资格进行垃圾回收。
大多数程序都不需要调用此方法。该方法设计为可取消大量任务的罕见应用程序使用。 调用此方法会用时间来换取空间:该方法的运行时间可能与n + c log n成正比,其中 n 是队列中的任务数,c 是取消的任务数。
请注意,可以从在此计时器安排的任务中调用此方法。
*
* @return the number of tasks removed from the queue.
* @since 1.5
*/
public int purge() {
int result = 0;
synchronized(queue) {
for (int i = queue.size(); i > 0; i--) {
if (queue.get(i).state == TimerTask.CANCELLED) {
queue.quickRemove(i);
result++;
}
}
if (result != 0)
queue.heapify();
}
return result;
}
java.util.TimerTask
基本用法
可通过继承实现其run方法
public class TimerTask1 extends java.util.TimerTask{
@Override
public void run() {
//在这里写入想执行的代码
System.out.println("世界你好");
}
}
new , 然后放入Tasker
TimerTask1 timeTask1 = new TimerTask1();
Timer timer1 = new Timer();
timer1.schedule(timeTask1, 0, 1000);
基本原理和方法
基本原理
TimerTask 继承自 Runnable
package java.util;
/**
* A task that can be scheduled for one-time or repeated execution by a
* {@link Timer}.
*
* <p>A timer task is <em>not</em> reusable. Once a task has been scheduled
* for execution on a {@code Timer} or cancelled, subsequent attempts to
* schedule it for execution will throw {@code IllegalStateException}.
*
* @author Josh Bloch
* @since 1.3
*/
public abstract class TimerTask implements Runnable {......}
TimerTask 的几种状态
/**
* The state of this task, chosen from the constants below.
* 保存任务状态的属性
*/
int state = VIRGIN; // 初始值 = 未排程
/**
* This task has not yet been scheduled.
* 未排程 , 没有放入Timer
*/
static final int VIRGIN = 0;
/**
* This task is scheduled for execution. If it is a non-repeating task,
* it has not yet been executed.
* 已排程。 如果它是非重复任务,则尚未执行。
*/
static final int SCHEDULED = 1;
/**
* This non-repeating task has already executed (or is currently
* executing) and has not been cancelled.
* 此非重复任务已执行(或当前正在执行),尚未取消。
*/
static final int EXECUTED = 2;
/**
* This task has been cancelled (with a call to TimerTask.cancel).
* 此任务已被取消(通过调用TimerTask.cancel()方法)。
*/
static final int CANCELLED = 3;
TimerTask 的 方法
TimerTask 的 cancel() 方法
/**
* Cancels this timer task. If the task has been scheduled for one-time
* execution and has not yet run, or has not yet been scheduled, it will
* never run. If the task has been scheduled for repeated execution, it
* will never run again. (If the task is running when this call occurs,
* the task will run to completion, but will never run again.)
*
* <p>Note that calling this method from within the {@code run} method of
* a repeating timer task absolutely guarantees that the timer task will
* not run again.
*
* <p>This method may be called repeatedly; the second and subsequent
* calls have no effect.
*
* 取消此计时器任务。
* 如果任务已安排一次执行并且尚未运行,或者尚未安排,则它将永远不会运行。
* 如果计划将任务重复执行,则它将不再运行。 (如果在此调用发生时任务正在运行,则该任务将运行至完成,但将不再运行。)
请注意,从重复计时器任务的run方法中调用此方法绝对保证计时器任务不会再次运行。
可以重复调用此方法。 第二次及随后的调用均无效。 哈哈哈
*
* @return true if this task is scheduled for one-time execution and has
* not yet run, or this task is scheduled for repeated execution.
* Returns false if the task was scheduled for one-time execution
* and has already run, or if the task was never scheduled, or if
* the task was already cancelled. (Loosely speaking, this method
* returns {@code true} if it prevents one or more scheduled
* executions from taking place.)
* 返回 :
* 如果此任务计划为一次性执行但尚未运行,或者此任务计划重复执行,则为true。
* 如果计划将任务一次性执行并且已经运行,或者从未计划过任务,则返回false。
* 如果任务已被取消。 (松散地说,如果此方法阻止执行一个或多个计划的执行,则返回true。)
*
*/
public boolean cancel() {
synchronized(lock) {
boolean result = (state == SCHEDULED);
state = CANCELLED;
return result;
}
}
设置状态为退出, 如果是之前状态是 已排程 则返回true , 否则 false
TimerTask 和 Timer 都有 cancel()方法
TimerTask 的 scheduledExecutionTime()方法
/**
* Returns the <i>scheduled</i> execution time of the most recent
* <i>actual</i> execution of this task. (If this method is invoked
* while task execution is in progress, the return value is the scheduled
* execution time of the ongoing task execution.)
*
* <p>This method is typically invoked from within a task's run method, to
* determine whether the current execution of the task is sufficiently
* timely to warrant performing the scheduled activity:
* <pre>{@code
* public void run() {
* if (System.currentTimeMillis() - scheduledExecutionTime() >=
* MAX_TARDINESS)
* return; // Too late; skip this execution.
* // Perform the task
* }
* }</pre>
* This method is typically <i>not</i> used in conjunction with
* <i>fixed-delay execution</i> repeating tasks, as their scheduled
* execution times are allowed to drift over time, and so are not terribly
* significant.
*
* @return the time at which the most recent execution of this task was
* scheduled to occur, in the format returned by Date.getTime().
* The return value is undefined if the task has yet to commence
* its first execution.
* @see Date#getTime()
*/
public long scheduledExecutionTime() {
synchronized(lock) {
return (period < 0 ? nextExecutionTime + period
: nextExecutionTime - period);
}
}
返回下次执行时间 减去 周期
使用schedule时period被取负 scheduleAtFixedRate则正
Timer的缺点
- 如同一个座位只能坐一个人, 否则就会拥挤
- 单独一个Timer没法造飞机坦克和大炮