Java实现方法超时未调用触发空闲事件的安全高效方案
实现并发环境下Service类的空闲触发机制
在并发环境下,多线程调用
Service类的do(Foo foo)方法。要求该方法连续5秒未被调用时,触发onIdle()空闲事件;若后续仍连续5秒未被调用,onIdle()不再重复触发;当方法再次被调用时,需重新启动空闲定时器。现有代码如下:
public class Service { // 注意:Java中do是关键字,实际开发建议重命名为doProcess等合法名称 public void do(Foo foo) { // process foo } private void onIdle() { } }
同时也接受移除「不重复触发onIdle()」要求的更优方案。
一、满足「不重复触发onIdle()」要求的安全高效实现
这是一个典型的并发空闲检测场景,我们可以用ScheduledExecutorService调度空闲任务,配合原子类跟踪状态,既保证线程安全,又能精准控制触发逻辑。
实现代码如下:
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; public class Service { // 单线程调度器,避免多线程任务冲突 private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); // 跟踪当前的空闲定时任务,用于取消和重置 private final AtomicReference<ScheduledFuture<?>> idleTask = new AtomicReference<>(); // 标记是否已触发过空闲事件,防止重复执行 private final AtomicBoolean isIdleTriggered = new AtomicBoolean(false); // 空闲阈值:5秒 private static final long IDLE_THRESHOLD_SECONDS = 5; // 注意:实际开发请重命名该方法,避免使用Java关键字do public void do(Foo foo) { // 执行业务逻辑 processFoo(foo); // 重置空闲标记:如果之前触发过onIdle,现在重新调用就恢复可触发状态 isIdleTriggered.set(false); // 取消之前的定时任务(如果存在且未执行) ScheduledFuture<?> previousTask = idleTask.getAndSet(null); if (previousTask != null && !previousTask.isDone()) { previousTask.cancel(false); } // 重新调度新的空闲检测任务 ScheduledFuture<?> newTask = scheduler.schedule(this::checkAndTriggerIdle, IDLE_THRESHOLD_SECONDS, TimeUnit.SECONDS); idleTask.set(newTask); } private void checkAndTriggerIdle() { // 仅当未触发过空闲事件时,才执行onIdle并标记为已触发 if (isIdleTriggered.compareAndSet(false, true)) { onIdle(); } } private void processFoo(Foo foo) { // 这里写原有的业务处理逻辑 } private void onIdle() { // 空闲事件的处理逻辑 } // 实际应用中需提供关闭方法,优雅终止调度器 public void shutdown() { scheduler.shutdown(); } }
方案说明:
- 用单线程的
ScheduledExecutorService保证定时任务串行执行,避免并发冲突; AtomicReference原子性跟踪定时任务,每次调用业务方法时都会取消旧任务、启动新任务,确保空闲计时从最后一次调用开始计算;AtomicBoolean原子性标记空闲状态,只有从活跃状态切换到空闲时才触发onIdle(),后续连续空闲不会重复执行;- 所有状态操作都是线程安全的,适配多线程调用场景。
二、移除「不重复触发onIdle()」要求的更优方案
如果不需要限制onIdle()的重复触发,我们可以简化实现,去掉状态跟踪逻辑,代码更简洁,性能也略有提升。
实现代码如下:
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class Service { private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final AtomicReference<ScheduledFuture<?>> idleTask = new AtomicReference<>(); private static final long IDLE_THRESHOLD_SECONDS = 5; // 注意:实际开发请重命名该方法,避免使用Java关键字do public void do(Foo foo) { processFoo(foo); // 取消之前的定时任务 ScheduledFuture<?> previousTask = idleTask.getAndSet(null); if (previousTask != null && !previousTask.isDone()) { previousTask.cancel(false); } // 重新调度空闲任务,到期直接触发onIdle ScheduledFuture<?> newTask = scheduler.schedule(this::onIdle, IDLE_THRESHOLD_SECONDS, TimeUnit.SECONDS); idleTask.set(newTask); } private void processFoo(Foo foo) { // 业务处理逻辑 } private void onIdle() { // 空闲事件处理逻辑 } public void shutdown() { scheduler.shutdown(); } }
方案说明:
- 移除了空闲状态标记,减少了原子操作开销;
- 每次连续空闲5秒都会触发
onIdle(),满足移除限制后的需求; - 依然通过原子类和单线程调度器保证线程安全,适配多线程调用场景。
内容的提问来源于stack exchange,提问作者Rüdiger Schulz
相关产品推荐
相关产品推荐

