Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

implementing debounce in Java

Tags:

java

algorithm

For some code I'm writing I could use a nice general implementation of debounce 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     } } 

When call() is called multiple times in interval 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?
like image 333
levinalex Avatar asked Jan 20 '11 00:01

levinalex


People also ask

What is the purpose of debounce?

The debounce() function forces a function to wait a certain amount of time before running again. The function is built to limit the number of times a function is called.

What is debounce in programming?

Bouncing is the tendency of any two metal contacts in an electronic device to generate multiple signals as the contacts close or open; debouncing is any kind of hardware device or software that ensures that only a single signal will be acted upon for a single opening or closing of a contact.

What is UI Debounce?

util. debounce. Wraps a function to allow it to be called, at most, once for each sequence of calls fired repeatedly so long as they are fired less than a specified interval apart (in milliseconds). This can be used to reduce the number of invocations of an expensive function while ensuring it eventually runs.


2 Answers

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

and callback interface:

public interface Callback<T> {     public void call(T t); } 
like image 54
Eyal Schneider Avatar answered Oct 05 '22 20:10

Eyal Schneider


Here's my implementation:

public class Debouncer {     private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();     private final ConcurrentHashMap<Object, Future<?>> delayedMap = new ConcurrentHashMap<>();      /**      * Debounces {@code callable} by {@code delay}, i.e., schedules it to be executed after {@code delay},      * or cancels its execution if the method is called with the same key within the {@code delay} again.      */     public void debounce(final Object key, final Runnable runnable, long delay, TimeUnit unit) {         final Future<?> prev = delayedMap.put(key, scheduler.schedule(new Runnable() {             @Override             public void run() {                 try {                     runnable.run();                 } finally {                     delayedMap.remove(key);                 }             }         }, delay, unit));         if (prev != null) {             prev.cancel(true);         }     }      public void shutdown() {         scheduler.shutdownNow();     } } 

Example usage:

final Debouncer debouncer = new Debouncer(); debouncer.debounce(Void.class, new Runnable() {     @Override public void run() {         // ...     } }, 300, TimeUnit.MILLISECONDS); 
like image 39
simon04 Avatar answered Oct 05 '22 20:10

simon04