基本介绍
ScheduledExecutorService
是 Java 中用于调度任务的执行器服务,它允许在给定的延迟后或定期执行任务。相比于 Timer
和 TimerTask
,ScheduledExecutorService
提供了更灵活和强大的功能,并能更好地处理多线程环境下的任务调度。
核心方法
ScheduledExecutorService
提供了以下核心方法:
- schedule(Runnable command, long delay, TimeUnit unit):
在给定的延迟后执行一次任务。 - schedule(Callable callable, long delay, TimeUnit unit):
在给定的延迟后执行一次任务,并返回一个结果。 - scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):
从给定的初始延迟后开始,每隔指定的时间段执行一次任务。 - scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit):
从给定的初始延迟后开始,每次任务执行完成后,等待指定的延迟再执行下一次任务。
使用示例
以下是一个使用 ScheduledExecutorService
的示例,展示了如何定期生成令牌并消费令牌。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class TokenBucket {
private final int capacity; // 桶的容量
private final int tokensPerSecond; // 每秒生成的令牌数
private int tokens; // 当前令牌数
private final ScheduledExecutorService scheduler;
public TokenBucket(int tokensPerSecond, int capacity) {
this.capacity = capacity;
this.tokensPerSecond = tokensPerSecond;
this.tokens = 0;
this.scheduler = Executors.newScheduledThreadPool(1);
startTokenGeneration();
}
// 定期生成令牌
private void startTokenGeneration() {
scheduler.scheduleAtFixedRate(() -> {
synchronized (this) {
tokens = Math.min(capacity, tokens + tokensPerSecond);
System.out.println("Tokens generated: " + tokens);
}
}, 0, 1, TimeUnit.SECONDS);
}
// 消耗令牌
public synchronized boolean consume(int numTokens) {
if (tokens >= numTokens) {
tokens -= numTokens;
return true;
}
return false;
}
public static void main(String[] args) throws InterruptedException {
TokenBucket bucket = new TokenBucket(1, 10);
Runnable sendPacket = () -> {
int packetSize = 1;
if (bucket.consume(packetSize)) {
System.out.println("Packet of size " + packetSize + " sent.");
} else {
System.out.println("Packet of size " + packetSize + " dropped.");
}
};
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(sendPacket, 0, 100, TimeUnit.MILLISECONDS);
// 运行10秒钟
Thread.sleep(10000);
executor.shutdown();
bucket.scheduler.shutdown();
}
}
代码说明
- TokenBucket 类:
capacity
:令牌桶的最大容量。tokensPerSecond
:每秒生成的令牌数。tokens
:当前令牌数。scheduler
:用于定期生成令牌的调度器。
- startTokenGeneration 方法:使用
scheduleAtFixedRate
每秒生成一次令牌,更新令牌数。 - consume 方法:尝试消耗指定数量的令牌,如果令牌数足够则消耗并返回
true
,否则返回false
。 - main 方法:创建一个令牌桶实例,定期尝试发送数据包,并在控制台输出发送结果。
应用场景
- 定时任务执行:定期执行某些任务,如定时备份、日志清理等。
- 周期性任务调度:如定时刷新缓存、定时发送心跳包等。
- 延迟任务执行:在某个延迟后执行一次任务。
优缺点
优点:
- 强大的定时功能:相比于
Timer
,ScheduledExecutorService
提供了更强大和灵活的定时任务执行能力。 - 多线程支持:能够更好地处理多线程环境下的任务调度。
- 灵活性高:可以轻松实现延迟执行、定期执行等各种任务调度需求。
缺点:
- 复杂性:相比于
Timer
,ScheduledExecutorService
的使用略显复杂。 - 资源消耗:需要管理线程池,可能会带来额外的资源消耗。
面试问答
问:什么是 ScheduledExecutorService
?
答:ScheduledExecutorService
是 Java 中用于调度任务的执行器服务,它允许在给定的延迟后或定期执行任务。
问:如何使用 ScheduledExecutorService
实现定期任务?
答:可以使用 scheduleAtFixedRate
方法来实现定期任务调度,或者使用 scheduleWithFixedDelay
方法来实现任务完成后的延迟调度。
问:ScheduledExecutorService
** 和 Timer
有什么区别?**
答:ScheduledExecutorService
提供了更灵活和强大的功能,能够更好地处理多线程环境下的任务调度,而 Timer
相对简单,但在多线程环境下的表现较差。
问:如何优雅地关闭 ScheduledExecutorService
?
答:可以使用 shutdown
方法来关闭 ScheduledExecutorService
,它会停止接受新任务,并等待已经提交的任务执行完成。可以使用 shutdownNow
方法立即停止所有任务。