对于我正在编写的某些代码,我可以使用debounceJava中的一个不错的通用实现。
debounce
public interface Callback { public void call(Object arg); } class Debouncer implements Callback { public Debouncer(Callback c, int interval) { ... } public void call(Object arg) { // should forward calls with the same arguments to the callback c // but batch multiple calls inside `interval` to a single one } }
当call()被称为多次interval毫秒具有相同参数的回调函数应调用一次。
call()
interval
可视化:
Debouncer#call xxx x xxxxxxx xxxxxxxxxxxxxxx Callback#call x x x (interval is 2)
请考虑以下线程安全解决方案。请注意,锁定粒度是在密钥级别上的,因此仅同一密钥上的调用会相互阻塞。它还处理在调用call(K)时发生的密钥K过期的情况。
public class Debouncer <T> { private final ScheduledExecutorService sched = Executors.newScheduledThreadPool(1); private final ConcurrentHashMap<T, TimerTask> delayedMap = new ConcurrentHashMap<T, TimerTask>(); private final Callback<T> callback; private final int interval; public Debouncer(Callback<T> c, int interval) { this.callback = c; this.interval = interval; } public void call(T key) { TimerTask task = new TimerTask(key); TimerTask prev; do { prev = delayedMap.putIfAbsent(key, task); if (prev == null) sched.schedule(task, interval, TimeUnit.MILLISECONDS); } while (prev != null && !prev.extend()); // Exit only if new task was added to map, or existing task was extended successfully } public void terminate() { sched.shutdownNow(); } // The task that wakes up when the wait time elapses private class TimerTask implements Runnable { private final T key; private long dueTime; private final Object lock = new Object(); public TimerTask(T key) { this.key = key; extend(); } public boolean extend() { synchronized (lock) { if (dueTime < 0) // Task has been shutdown return false; dueTime = System.currentTimeMillis() + interval; return true; } } public void run() { synchronized (lock) { long remaining = dueTime - System.currentTimeMillis(); if (remaining > 0) { // Re-schedule task sched.schedule(this, remaining, TimeUnit.MILLISECONDS); } else { // Mark as terminated and invoke callback dueTime = -1; try { callback.call(key); } finally { delayedMap.remove(key); } } } } }