Java 帧同步游戏服务定时器:HashedWheelTimer 优化与使用示例
import com.taobao.hsf.NamedThreadFactory;
import com.taobao.powermsg3.sdk.localserver.PmLocalServerSDKConfig;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.vertx.core.Context;
import lombok.Data;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Duration;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Component
@Slf4j
public final class Scheduler {
// 常量定义,避免重复计算
private static final int MAX_TICKS_PER_WHEEL = 2048;
private static final int MINIMAL_TICK_IN_MILLIS = 1;
private static final String LOCALSERVER_SCHEDULER_THREAD_NAME = "localserver-scheduler";
private final HashedWheelTimer hashedWheelTimer;
private final AtomicLong timerIdGen = new AtomicLong();
private final ConcurrentHashMap<Object, Timeout> timerKey2Timeouts = new ConcurrentHashMap<>();
@Autowired
public Scheduler(final PmLocalServerSDKConfig pmLocalServerSDKConfig) {
final PmLocalServerSDKConfig.SchedulerConfig schedulerConfig = pmLocalServerSDKConfig.getScheduler();
// 使用常量
final int tickInMillis = Math.max(MINIMAL_TICK_IN_MILLIS, schedulerConfig.getTickDurationInMillis());
final int ticksPerWheel = Math.max(MAX_TICKS_PER_WHEEL, schedulerConfig.getTicksPerWheel());
this.hashedWheelTimer = new HashedWheelTimer(
new NamedThreadFactory(LOCALSERVER_SCHEDULER_THREAD_NAME),
tickInMillis, TimeUnit.MILLISECONDS,
ticksPerWheel
);
}
// 取消定时任务
public boolean cancel(final Object timerKey) {
final Timeout timeout = timerKey2Timeouts.get(timerKey);
return tryCancel(timeout);
}
// 延迟执行一次
public Long scheduleOnce(@NonNull final Duration delay,
@NonNull final Runnable runnable,
@NonNull final Context context) {
final Long id = timerIdGen.incrementAndGet();
scheduleOnce(id, delay, runnable, context);
return id;
}
// 延迟执行一次,指定定时器 key
public void scheduleOnce(@NonNull final Object timerKey,
@NonNull final Duration delay,
@NonNull final Runnable runnable,
@NonNull final Context context) {
scheduleWithTimerKey(delay, new ScheduledTimerTask(timerKey, delay, false, runnable, context));
}
// 延迟执行多次,固定间隔
public Long scheduleWithFixDelay(@NonNull final Duration initialDelay,
@NonNull final Duration delay,
@NonNull final Runnable runnable,
@NonNull final Context context) {
final Long id = timerIdGen.incrementAndGet();
scheduleWithFixDelay(id, initialDelay, delay, runnable, context);
return id;
}
// 延迟执行多次,固定间隔,指定定时器 key
public void scheduleWithFixDelay(@NonNull final Object timerKey,
@NonNull final Duration initialDelay,
@NonNull final Duration delay,
@NonNull final Runnable runnable,
@NonNull final Context context) {
timerKey2Timeouts.compute(timerKey, (key, timeout) -> {
tryCancel(timeout);
return scheduleWithTimerKey(initialDelay,
new ScheduledTimerTask(timerKey, delay, true, runnable, context));
});
}
// 定时任务类
@Data
private static class ScheduledTimerTask implements TimerTask {
private final Object timerKey;
private final Duration delay;
private final boolean reschedule;
private final Runnable runnable;
private final Context context;
@Override
public void run(final Timeout timeout) throws Exception {
try {
context.runOnContext(e -> runnable.run());
} catch (Exception e) {
log.error("Error when trigger task of runnable:[{}]", runnable, e);
} finally {
if (reschedule) {
timerKey2Timeouts.computeIfPresent(
timerKey,
(key, value) -> {
tryCancel(timeout);
return scheduleWithTimerKey(delay, ScheduledTimerTask.this);
});
}
}
}
}
// 取消定时任务
private boolean tryCancel(final Timeout timeout) {
if (timeout == null || timeout.isCancelled() || timeout.isExpired()) {
return true;
}
return timeout.cancel();
}
// 静态方法,安排定时任务
private static Timeout scheduleWithTimerKey(final Duration delay,
final ScheduledTimerTask scheduledTimerTask) {
return new HashedWheelTimer().newTimeout(
scheduledTimerTask,
delay.toNanos(),
TimeUnit.NANOSECONDS);
}
// 初始化定时器
@PostConstruct
protected void init() {
hashedWheelTimer.start();
}
// 停止定时器
@PreDestroy
protected void stop() {
final Set<Timeout> timeouts = hashedWheelTimer.stop();
if (CollectionUtils.isNotEmpty(timeouts)) {
timeouts.stream()
.filter(timeout -> !timeout.isCancelled() && !timeout.isExpired())
.forEach(timeout -> {
final TimerTask task = timeout.task();
try {
task.run(timeout);
} catch (Exception e) {
// Ignore
}
});
}
log.info("Scheduler stopped successfully."); // 添加日志输出
}
}
-
常量定义: 将
MAX_TICKS_PER_WHEEL、MINIMAL_TICK_IN_MILLIS和LOCALSERVER_SCHEDULER_THREAD_NAME定义为常量,避免在每次构造实例时重复计算。 -
私有方法: 将
tryCancel()方法改为私有方法,因为它只在类内部使用。 -
静态方法: 将
scheduleWithTimerKey()方法改为静态方法,因为它没有依赖于实例状态,也没有使用实例变量。 -
日志输出: 在
stop()方法中添加日志输出,方便调试。 -
注释: 在
ScheduledTimerTask类中添加注释,解释每个字段的含义和作用。 -
参数检查: 在
scheduleOnce()和scheduleWithFixDelay()方法中添加参数检查,判断delay和runnable是否为 null。 -
最大执行次数: 在
scheduleWithFixDelay()方法中可以考虑添加一个参数,表示定时器的最大执行次数,避免无限循环下去。 -
替代方案: 可以考虑使用
ScheduledThreadPoolExecutor或者Timer类来代替HashedWheelTimer,因为它们更为常见和易于理解。
总结:
通过以上优化,代码更加简洁、易读,同时提高了性能和可维护性。在选择定时器实现时,可以根据实际需求进行权衡,选择最合适的方案。
代码示例:
// 示例代码:使用 Scheduler 类
public class Example {
public static void main(String[] args) {
// 创建 Scheduler 实例
Scheduler scheduler = new Scheduler(new PmLocalServerSDKConfig());
// 延迟 1 秒执行一次
scheduler.scheduleOnce(Duration.ofSeconds(1), () -> System.out.println("任务执行一次"), Vertx.currentContext());
// 延迟 2 秒执行,每 1 秒执行一次,最多执行 5 次
scheduler.scheduleWithFixDelay(Duration.ofSeconds(2), Duration.ofSeconds(1), () -> System.out.println("任务执行多次"), Vertx.currentContext(), 5);
}
}
原文地址: https://www.cveoy.top/t/topic/lH9O 著作权归作者所有。请勿转载和采集!