如何实现事件处理函数中指定代码段的间隔触发限制(单次执行后需等待指定时长才可再次执行)
这个需求其实很常见,就是给这段逻辑加个「冷却期」嘛~我给你几个实用的实现思路,你可以根据自己的项目环境挑合适的:
方案1:手动维护执行时间戳(无额外依赖)
如果不想引入第三方库,自己写逻辑最稳妥。核心就是记录最后一次执行的时间,每次触发事件时判断间隔是否够长。
首先在你的事件处理类里加两个成员变量:
// 设定冷却时长,比如10秒(单位:毫秒) private static final long COOLDOWN_MS = 10000L; // 记录最后一次执行的时间戳,用AtomicLong保证线程安全 private AtomicLong lastRunTime = new AtomicLong(0L);
然后修改你的事件处理方法:
@EventHandler(event = true) public void Event(getterEvent e) { Var1 v1 = e.getData(); Var2 v2 = e.getData(); if (v2.hasPermission()) return; // 这里原代码的判断逻辑看起来有点语法问题,建议检查下,比如是不是v1.getLocation() >= 128.00? if (v1.getData() == Data.Location >= 128.00) { if(e.DataValue()){ long now = System.currentTimeMillis(); // 判断当前时间和上次执行时间的间隔是否超过冷却期 if (now - lastRunTime.get() >= COOLDOWN_MS) { // --- 此处执行你的具体操作 --- // 更新最后执行时间 lastRunTime.set(now); } } e.setCancelled(true); } }
方案2:用Guava RateLimiter(简洁省心)
如果你的项目已经用了Guava库,直接用RateLimiter来做限流会更简洁,它帮你封装好了所有时间计算逻辑。
先定义一个RateLimiter实例:
// 设定每10秒允许执行1次(1.0/10表示每秒0.1次) private static final RateLimiter rateLimiter = RateLimiter.create(1.0 / 10);
然后修改事件处理里的逻辑:
@EventHandler(event = true) public void Event(getterEvent e) { Var1 v1 = e.getData(); Var2 v2 = e.getData(); if (v2.hasPermission()) return; if (v1.getData() == Data.Location >= 128.00) { if(e.DataValue()){ // 尝试获取执行许可,能拿到就执行 if (rateLimiter.tryAcquire()) { // --- 此处执行你的具体操作 --- } } e.setCancelled(true); } }
这个方式代码最简洁,而且RateLimiter本身就考虑了线程安全,不用自己操心并发问题。
方案3:用定时任务锁定状态(精确控制)
如果需要更严格的冷却逻辑——比如执行一次后,必须等满10秒才能再次执行,不管中间触发多少次事件都不行——可以用定时任务来锁定执行状态。
首先在类里加成员变量:
private static final long COOLDOWN_MS = 10000L; // 标记是否允许执行,用AtomicBoolean保证线程安全 private AtomicBoolean canExecute = new AtomicBoolean(true); // 单线程定时任务池,避免多线程混乱 private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
然后修改事件处理方法:
@EventHandler(event = true) public void Event(getterEvent e) { Var1 v1 = e.getData(); Var2 v2 = e.getData(); if (v2.hasPermission()) return; if (v1.getData() == Data.Location >= 128.00) { if(e.DataValue()){ // 如果当前允许执行,先把状态改成不可执行,再执行操作 if (canExecute.compareAndSet(true, false)) { // --- 此处执行你的具体操作 --- // 启动定时任务,冷却期到了再把状态改回可执行 scheduler.schedule(() -> canExecute.set(true), COOLDOWN_MS, TimeUnit.MILLISECONDS); } } e.setCancelled(true); } }
如果是Spring项目,建议用@Scheduled注解代替手动创建线程池,记得在类销毁时关闭线程池,避免资源泄漏:
@PreDestroy public void cleanUp() { scheduler.shutdown(); }
小提醒
不管用哪种方案,都要注意线程安全——因为事件处理方法很可能被多个线程同时调用,所以用来控制状态的变量(比如时间戳、可执行标记)最好用原子类(AtomicLong/AtomicBoolean),或者加同步锁。
内容的提问来源于stack exchange,提问作者Firav
相关产品推荐
相关产品推荐

