implementing debounce in Java

前端 未结 8 591
心在旅途
心在旅途 2020-11-30 04:09

For some code I\'m writing I could use a nice general implementation of debounce in Java.

public interface Callback {
  public void call(Object          


        
相关标签:
8条回答
  • 2020-11-30 04:49

    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);
              }
            }
          }
        }  
      }
    
    0 讨论(0)
  • 2020-11-30 04:51

    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);
    
    0 讨论(0)
提交回复
热议问题