一、定时任务
在 java.util
包下提供了 Timer
与 TimerTask
用于提交定时任务,其中 Timer
用于管理定时任务,TimerTask
创建定义定时任务的具体执行内容。
1. 定时任务
TimerTask
使用类似于线程,新建任务类 CustomTask
继承 TimerTask
类并重写 run()
方法,在 run()
方法中定义任务的具体执行逻辑。
如下示例中我定义了一个定时任务打印当前系统时间并自增计数器 num
。
class CustomTask extends TimerTask {
private String taskName;
public CustomTask(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
String nowadays = formatter.format(LocalDateTime.now());
System.out.printf("Schedule [%s] active, current time: %s%n", this.taskName, nowadays);
} catch (Exception ex) {
System.out.println("Error running thread " + ex.getMessage());
}
num.getAndIncrement();
}
}
2. 任务提交
完成定时任务的创建之后即可通过 Timer
类的 schedule()
方法进行提交,Timer 中维护了一个默认大小为 128 的工作队列用于接收传入定时任务。
schedule(task, delay, period)
方法的三个入参描述参考下表。
方法 | 描述 |
---|---|
task | 需要指定的定时任务。 |
delay | long 类型,任务首次触发时间与当前的时间差。 |
period | long 类型,定时任务间隔周期。 |
如下示例中通过 Timer
提交了一个定时任务,并设置在 2
秒后触发,任务每隔 5
秒执行一次。
public class ScheduleTest {
private volatile AtomicInteger num = new AtomicInteger(0);
@Test
public void timeTaskDemo() throws InterruptedException {
Timer time = new Timer();
long delay = TimeUnit.SECONDS.toMillis(2);
long period = TimeUnit.SECONDS.toMillis(5);
time.schedule(new CustomTask(), delay, period);
while (true) {
if (num.get() > 3) {
time.cancel();
System.out.println("Cancel time task.");
break;
}
}
// purge(): Remove the cancelled task from time queue.
// If timer queue is empty that is eligible for gc
time.purge();
}
}
除了 schedule()
方法 Timer
类中还提供其它相应的操作,具体信息参考下表:
方法 | 描述 |
---|---|
cancel() | 清空排队中定时任务并不再接收新提交任务,但是正在执行的任务不会中断。 |
purge() | 清空队列中已经被 cancel 的任务,当队列为空时即可被 GC。 |
二、延时类
1. 基本介绍
在 Java
中提供了 Delayed
接口类以供延迟队列,延迟队列中的元素必须实现 Delayed
接口。
Delayed
接口中包含了重要接口方法 getDelay()
,新建自定义延迟任务类 DelayTask
并重新该方法,当延迟队列 DelayQueue
在获取元素即会调用该方法。
class DelayTask<T> implements Delayed {
private final long EXPIRE = TimeUnit.SECONDS.toMillis(10);
private T t;
private long startTime;
public DelayTask(T t, long startTime) {
this.t = t;
this.startTime = startTime + EXPIRE;
}
/**
* Get element will activate getDelay().
* <p>
* If "interval" lower the zero then retrieved to get.
*/
@Override
public long getDelay(TimeUnit unit) {
long interval = startTime - System.currentTimeMillis();
// convert time interval to milliseconds
return unit.convert(interval, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
DelayTask<T> task = (DelayTask<T>) o;
return Ints.saturatedCast(startTime - task.startTime);
}
}
2. 任务提交
完成延迟任务对象定义之后即可创建延迟队列,其常用方法参考下表:
方法 | 作用 |
---|---|
add() | 添加延时任务至队列,返回 boolean 值。 |
put() | 添加延时任务至队列,无返回值。 |
take() | 以阻塞的方式获取队列元素。 |
这里重点介绍一下 take()
方法的作用效果。
当
take()
获取队列元素时将会触发Delayed
接口的getDelay()
方法。
- 若返回值小于等于
0
,返回队列元素。- 若返回值大于
0
,则访问队头的后一位元素。- 若队列中所有元素的
getDelay()
返回值皆大于0
,take()
将一直处于阻塞状态。
@Test
public void delayDemo() throws InterruptedException {
DelayQueue<DelayTask<String>> delayQueue = new DelayQueue<>();
delayQueue.put(new DelayTask<>("Alex", System.currentTimeMillis()));
TimeUnit.MILLISECONDS.sleep(1000);
delayQueue.put(new DelayTask<>("Beth", System.currentTimeMillis()));
/*
* task(): Get queue head element and active getDelay() result
* ==> 1. result <= 0: return element.
* ==> 2. result > 0: skit element and visit next, if all > 0 then block.
*/
DelayTask<String> task = delayQueue.take();
System.out.println("Task: " + task);
}
三、定时线程池
1. 基本介绍
上述提到 Timer 类中维护了一个工作队列并以单线程执行定时任务,而 ScheduledExecutorService
可用于创建定时线程任务资源池。
ScheduledExecutorService
提交任务不再限制必须继承于 TimerTask
,任务类继承于线程同样允许。
2. 任务提交
通过 newScheduledThreadPool()
创建定时任务线程池资源,通过构造器指定线程数。
方法 | 作用 |
---|---|
schedule() | 提交定时任务,仅执行一次。 |
scheduleAtFixedRate() | 提交定时任务,任务触发间隔周期是按上次任务开始时间计算。 |
scheduleWithFixedDelay() | 提交定时任务,任务触发间隔周期是按上次任务完成时间计算。 |
public void scheduledPoolDemo() throws Exception {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
int start = 1;
int interval = 3;
// 当前时间 1 秒后执行一次
executor.schedule(() -> {
System.out.println("Task-1 running.");
}, interval, TimeUnit.SECONDS);
// 当前时间 1 秒后执行, 且每隔 3 秒重复执行
// (间隔时间:上一次任务开始时计时)
executor.scheduleAtFixedRate(new Task("fixed-rate"), start, interval, TimeUnit.SECONDS);
// 当前时间 1 秒后执行, 上一任务执行完成 3 秒后重复执行
// (间隔时间:上一次任务结束时计时)
executor.scheduleWithFixedDelay(new Task("fixed-delay"), start, interval, TimeUnit.SECONDS);
executor.shutdown();
}
需要注意 scheduleAtFixedRate
与 scheduleWithFixedDelay
的区别。
- scheduleAtFixedRate()间隔时间是从上一个任务开始时计算,无论上个任务是否已经结束。
- scheduleWithFixedDelay()间隔时间是从上个任务完成时开始计算,只有当上个任务结束才会开始计时。