levinalex levinalex - 7 months ago 288
Java Question

implementing debounce in Java

For some code I'm writing I could use a nice general implementation of

in Java.

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

is called multiple times in
milliseconds with the same argument the callback function should be called exactly once.

A visualization:

Debouncer#call xxx x xxxxxxx xxxxxxxxxxxxxxx
Callback#call x x x (interval is 2)

  • Does (something like) this exist already in some Java standard library?

  • How would you implement that?


Please consider the following thread safe solution. Note that the lock granularity is on the key level, so that only calls on the same key block each other. It also handles the case of an expiration on key K which occurs while call(K) is called.

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() {

  // 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;

    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 {
          } finally {