Appels de méthode de limitation à M requêtes en N secondes

137

J'ai besoin d'un composant / classe qui limite l'exécution d'une méthode à un maximum d'appels M en N secondes (ou ms ou nanos, peu importe).

En d'autres termes, je dois m'assurer que ma méthode n'est pas exécutée plus de M fois dans une fenêtre glissante de N secondes.

Si vous ne connaissez pas la classe existante, n'hésitez pas à publier vos solutions / idées sur la manière dont vous implémenteriez cela.

vtrubnikov
la source
3
Il y a d'excellentes réponses à ce problème sur stackoverflow.com/questions/667508/…
skaffman
> Je dois m'assurer que ma méthode n'est pas> exécutée plus de M fois dans une> fenêtre glissante de N secondes. J'ai récemment écrit un article de blog sur la façon de faire cela dans .NET. Vous pourrez peut-être créer quelque chose de similaire en Java. Meilleure limitation de débit dans .NET
Jack Leitch
La question originale ressemble beaucoup au problème résolu dans ce billet de blog: [Java Multi-Channel Asynchronous Throttler] ( cordinc.com/blog/2010/04/java-multichannel-asynchronous.html ). Pour un taux de M appels en N secondes, le régulateur décrit dans ce blog garantit que tout intervalle de longueur N sur la chronologie ne contiendra pas plus de M appels.
Hbf

Réponses:

81

J'utiliserais un tampon en anneau d'horodatages avec une taille fixe de M. Chaque fois que la méthode est appelée, vous vérifiez l'entrée la plus ancienne, et si c'est moins de N secondes dans le passé, vous exécutez et ajoutez une autre entrée, sinon vous dormez pour le décalage horaire.

Michael Borgwardt
la source
4
Charmant. Juste ce que j'ai besoin. Les tentatives rapides montrent ~ 10 lignes pour implémenter cela et une empreinte mémoire minimale. Il suffit de penser à la sécurité des threads et à la mise en file d'attente des demandes entrantes.
vtrubnikov
5
C'est pourquoi vous utilisez DelayQueue de java.util.concurrent. Cela évite le problème de plusieurs threads agissant sur la même entrée.
erickson
5
Pour un cas à plusieurs threads, l'approche du seau à jetons peut être un meilleur choix, je pense.
Michael Borgwardt
1
Savez-vous comment cet algorithme est appelé s'il a un nom?
Vlado Pandžić
80

Ce qui a fonctionné pour moi, c'est Google Guava RateLimiter .

// Allow one request per second
private RateLimiter throttle = RateLimiter.create(1.0);

private void someMethod() {
    throttle.acquire();
    // Do something
}
schnatterer
la source
19
Je ne recommanderais pas cette solution car le Guava RateLimiter bloquera le thread et cela épuisera facilement le pool de threads.
kaviddiss
18
@kaviddiss si vous ne voulez pas bloquer, utiliseztryAquire()
slf
7
Le problème avec la mise en œuvre actuelle de RateLimiter (du moins pour moi) est qu'elle ne permet pas des périodes de temps supérieures à 1 seconde et donc des taux de par exemple 1 par minute.
John B
4
@John B Autant que je sache, vous pouvez réaliser 1 requête par minute avec RateLimiter en utilisant RateLimiter.create (60.0) + rateLimiter.acquire (60)
divideByZero
2
@radiantRazor Ratelimiter.create (1.0 / 60) et acquiert () réalise 1 appel par minute.
bizentass
30

Concrètement, vous devriez pouvoir l'implémenter avec un DelayQueue. Initialisez la file d'attente avec des M Delayedinstances dont le délai initialement défini sur zéro. À mesure que les demandes adressées à la méthode arrivent, takeun jeton entraîne le blocage de la méthode jusqu'à ce que l'exigence de limitation soit satisfaite. Lorsqu'un jeton a été pris, addun nouveau jeton dans la file d'attente avec un délai de N.

Erickson
la source
1
Oui, cela ferait l'affaire. Mais je n'aime pas particulièrement DelayQueue car il utilise (via PriortyQueue) un hachage binaire équilibré (ce qui signifie beaucoup de comparaisons offeret de croissance possible du tableau), et c'est un peu lourd pour moi. Je suppose que pour d'autres, cela pourrait être parfaitement correct.
vtrubnikov
5
En fait, dans cette application, étant donné que le nouvel élément ajouté au tas sera presque toujours l'élément maximum du tas (c'est-à-dire qu'il aura le plus long délai), une comparaison par ajout est généralement requise. De plus, le tableau ne grandira jamais si l'algorithme est correctement implémenté, car un élément n'est ajouté qu'après avoir pris un élément.
erickson
3
J'ai trouvé cela utile également dans les cas où vous ne voulez pas que les requêtes se produisent en grandes rafales en gardant la taille M et le délai N relativement petits de l'ordre de quelques millisecondes. par exemple. M = 5, N = 20 ms fournirait un passage de 250 / sec en rafale de kepping en taille 5.
FUD
Cette échelle va-t-elle à un million de tours par minute et lorsque les demandes simultanées sont autorisées? J'aurais besoin d'ajouter un million d'éléments retardés. Les cas de coin seront également élevés sur la latence - cas où plusieurs threads appellent poll () et il se verrouillerait à chaque fois.
Aditya Joshee
@AdityaJoshee Je ne l'ai pas comparé, mais si j'ai un peu de temps, j'essaierai d'avoir une idée de la surcharge. Une chose à noter cependant est que vous n'avez pas besoin de 1 million de jetons qui expirent en 1 seconde. Vous pouvez avoir 100 jetons qui expirent dans 10 millisecondes, 10 jetons qui expirent dans la milliseconde, etc. de limitation de débit. 1 million de tr / min ne sonne cependant pas comme une limitation. Si vous pouvez expliquer votre cas d'utilisation, j'aurai peut-être de meilleures idées.
erickson
21

Renseignez-vous sur l' algorithme du compartiment Token . Fondamentalement, vous avez un seau avec des jetons. Chaque fois que vous exécutez la méthode, vous prenez un jeton. S'il n'y a plus de jetons, vous bloquez jusqu'à ce que vous en obteniez un. Pendant ce temps, il y a un acteur externe qui reconstitue les jetons à un intervalle fixe.

Je ne connais pas de bibliothèque pour faire cela (ou quoi que ce soit de similaire). Vous pouvez écrire cette logique dans votre code ou utiliser AspectJ pour ajouter le comportement.

Kevin
la source
3
Merci pour la suggestion, algo intéressant. Mais ce n'est pas exactement ce dont j'ai besoin. Par exemple, je dois limiter l'exécution à 5 appels par seconde. Si j'utilise le seau de jetons et que 10 demandes arrivent en même temps, les 5 premiers appels prendront tous les jetons disponibles et s'exécuteront momentanément, tandis que les 5 appels restants seront exécutés à un intervalle fixe de 1/5 s. Dans une telle situation, j'ai besoin des 5 appels restants pour être exécutés en une seule rafale seulement après 1 seconde passe.
vtrubnikov
5
Et si vous ajoutiez 5 jetons au seau toutes les secondes (ou 5 - (5 restants) au lieu de 1 toutes les 1/5 de seconde?
Kevin
@Kevin non, cela ne me donnerait toujours pas d'effet de `` fenêtre coulissante ''
vtrubnikov
2
@valery oui ça le ferait. (N'oubliez pas de plafonner les jetons à M)
nos
pas besoin d'un "acteur extérieur". Tout peut être fait avec un seul thread si vous conservez des métadonnées sur les heures de demande.
Marsellus Wallace
8

Si vous avez besoin d'un limiteur de vitesse à fenêtre glissante basé sur Java qui fonctionnera sur un système distribué, vous pouvez jeter un œil au projet https://github.com/mokies/ratelimitj .

Une configuration basée sur Redis, pour limiter les demandes par IP à 50 par minute, ressemblerait à ceci:

import com.lambdaworks.redis.RedisClient;
import es.moki.ratelimitj.core.LimitRule;

RedisClient client = RedisClient.create("redis://localhost");
Set<LimitRule> rules = Collections.singleton(LimitRule.of(1, TimeUnit.MINUTES, 50)); // 50 request per minute, per key
RedisRateLimit requestRateLimiter = new RedisRateLimit(client, rules);

boolean overLimit = requestRateLimiter.overLimit("ip:127.0.0.2");

Voir https://github.com/mokies/ratelimitj/tree/master/ratelimitj-redis pour plus de détails sur la configuration de Redis.

user2326162
la source
5

Cela dépend de l'application.

Imaginez le cas dans lequel plusieurs threads veulent qu'un jeton effectue une action à débit global limité sans rafale autorisée (c'est-à-dire que vous voulez limiter 10 actions toutes les 10 secondes mais vous ne voulez pas que 10 actions se produisent dans la première seconde, puis restent 9 secondes arrêtées).

Le DelayedQueue a un inconvénient: l'ordre dans lequel les threads demandent des jetons peut ne pas être l'ordre dans lequel ils obtiennent leur demande exécutée. Si plusieurs threads sont bloqués en attente d'un jeton, il n'est pas clair lequel prendra le prochain jeton disponible. Vous pourriez même avoir des fils en attente pour toujours, à mon avis.

Une solution consiste à avoir un intervalle de temps minimum entre deux actions consécutives et à effectuer les actions dans le même ordre que celui demandé.

Voici une implémentation:

public class LeakyBucket {
    protected float maxRate;
    protected long minTime;
    //holds time of last action (past or future!)
    protected long lastSchedAction = System.currentTimeMillis();

    public LeakyBucket(float maxRate) throws Exception {
        if(maxRate <= 0.0f) {
            throw new Exception("Invalid rate");
        }
        this.maxRate = maxRate;
        this.minTime = (long)(1000.0f / maxRate);
    }

    public void consume() throws InterruptedException {
        long curTime = System.currentTimeMillis();
        long timeLeft;

        //calculate when can we do the action
        synchronized(this) {
            timeLeft = lastSchedAction + minTime - curTime;
            if(timeLeft > 0) {
                lastSchedAction += minTime;
            }
            else {
                lastSchedAction = curTime;
            }
        }

        //If needed, wait for our time
        if(timeLeft <= 0) {
            return;
        }
        else {
            Thread.sleep(timeLeft);
        }
    }
}
Duarte Meneses
la source
que veut minTimedire ici? Qu'est ce que ça fait? pouvez-vous expliquer à ce sujet?
flash le
minTimeest le laps de temps minimum qui doit s'écouler après la consommation d'un jeton avant que le jeton suivant puisse être consommé.
Duarte Meneses le
3

Bien que ce ne soit pas ce que vous avez demandé, ThreadPoolExecutorqui est conçu pour limiter à M requêtes simultanées au lieu de M requêtes en N secondes, peut également être utile.

Eugene Yokota
la source
2

J'ai implémenté un algorithme de limitation simple. Essayez ce lien, http://krishnaprasadas.blogspot.in/2012/05/throttling-algorithm.html

Un bref sur l'algorithme,

Cet algorithme utilise la capacité de Java Delayed Queue . Créez un objet retardé avec le retard attendu (ici 1000 / M pour TimeUnit milliseconde ). Mettez le même objet dans la file d'attente retardée qui nous fournira la fenêtre en mouvement. Ensuite, avant chaque appel de méthode, prenez l'objet de la file d'attente, take est un appel bloquant qui ne reviendra qu'après le délai spécifié, et après l'appel de méthode, n'oubliez pas de mettre l'objet dans la file d'attente avec le temps mis à jour (ici les millisecondes actuelles) .

Ici, nous pouvons également avoir plusieurs objets retardés avec un retard différent. Cette approche fournira également un débit élevé.

Krishas
la source
6
Vous devriez publier un résumé de votre algorithme. Si votre lien disparaît, votre réponse devient inutile.
jwr
Merci, j'ai ajouté le mémoire.
Krishas
1

Mon implémentation ci-dessous peut gérer une précision de temps de requête arbitraire, elle a une complexité de temps O (1) pour chaque requête, ne nécessite aucun tampon supplémentaire, par exemple la complexité de l'espace O (1), en outre, elle ne nécessite pas de thread d'arrière-plan pour libérer le jeton, à la place les jetons sont libérés en fonction du temps écoulé depuis la dernière demande.

class RateLimiter {
    int limit;
    double available;
    long interval;

    long lastTimeStamp;

    RateLimiter(int limit, long interval) {
        this.limit = limit;
        this.interval = interval;

        available = 0;
        lastTimeStamp = System.currentTimeMillis();
    }

    synchronized boolean canAdd() {
        long now = System.currentTimeMillis();
        // more token are released since last request
        available += (now-lastTimeStamp)*1.0/interval*limit; 
        if (available>limit)
            available = limit;

        if (available<1)
            return false;
        else {
            available--;
            lastTimeStamp = now;
            return true;
        }
    }
}
tonywl
la source
0

Essayez d'utiliser cette approche simple:

public class SimpleThrottler {

private static final int T = 1; // min
private static final int N = 345;

private Lock lock = new ReentrantLock();
private Condition newFrame = lock.newCondition();
private volatile boolean currentFrame = true;

public SimpleThrottler() {
    handleForGate();
}

/**
 * Payload
 */
private void job() {
    try {
        Thread.sleep(Math.abs(ThreadLocalRandom.current().nextLong(12, 98)));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.err.print(" J. ");
}

public void doJob() throws InterruptedException {
    lock.lock();
    try {

        while (true) {

            int count = 0;

            while (count < N && currentFrame) {
                job();
                count++;
            }

            newFrame.await();
            currentFrame = true;
        }

    } finally {
        lock.unlock();
    }
}

public void handleForGate() {
    Thread handler = new Thread(() -> {
        while (true) {
            try {
                Thread.sleep(1 * 900);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                currentFrame = false;

                lock.lock();
                try {
                    newFrame.signal();
                } finally {
                    lock.unlock();
                }
            }
        }
    });
    handler.start();
}

}

SergeZ
la source
0

Apache Camel prend également en charge le mécanisme Throttler comme suit:

from("seda:a").throttle(100).asyncDelayed().to("seda:b");
gtonique
la source
0

Ceci est une mise à jour du code LeakyBucket ci-dessus. Cela fonctionne pour plus de 1000 requêtes par seconde.

import lombok.SneakyThrows;
import java.util.concurrent.TimeUnit;

class LeakyBucket {
  private long minTimeNano; // sec / billion
  private long sched = System.nanoTime();

  /**
   * Create a rate limiter using the leakybucket alg.
   * @param perSec the number of requests per second
   */
  public LeakyBucket(double perSec) {
    if (perSec <= 0.0) {
      throw new RuntimeException("Invalid rate " + perSec);
    }
    this.minTimeNano = (long) (1_000_000_000.0 / perSec);
  }

  @SneakyThrows public void consume() {
    long curr = System.nanoTime();
    long timeLeft;

    synchronized (this) {
      timeLeft = sched - curr + minTimeNano;
      sched += minTimeNano;
    }
    if (timeLeft <= minTimeNano) {
      return;
    }
    TimeUnit.NANOSECONDS.sleep(timeLeft);
  }
}

et le unittest pour ci-dessus:

import com.google.common.base.Stopwatch;
import org.junit.Ignore;
import org.junit.Test;

import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class LeakyBucketTest {
  @Test @Ignore public void t() {
    double numberPerSec = 10000;
    LeakyBucket b = new LeakyBucket(numberPerSec);
    Stopwatch w = Stopwatch.createStarted();
    IntStream.range(0, (int) (numberPerSec * 5)).parallel().forEach(
        x -> b.consume());
    System.out.printf("%,d ms%n", w.elapsed(TimeUnit.MILLISECONDS));
  }
}
Peterreilly
la source
que minTimeNanosignifie ici? peux-tu expliquer?
flash le
0

Voici une petite version avancée du limiteur de débit simple

/**
 * Simple request limiter based on Thread.sleep method.
 * Create limiter instance via {@link #create(float)} and call {@link #consume()} before making any request.
 * If the limit is exceeded cosume method locks and waits for current call rate to fall down below the limit
 */
public class RequestRateLimiter {

    private long minTime;

    private long lastSchedAction;
    private double avgSpent = 0;

    ArrayList<RatePeriod> periods;


    @AllArgsConstructor
    public static class RatePeriod{

        @Getter
        private LocalTime start;

        @Getter
        private LocalTime end;

        @Getter
        private float maxRate;
    }


    /**
     * Create request limiter with maxRate - maximum number of requests per second
     * @param maxRate - maximum number of requests per second
     * @return
     */
    public static RequestRateLimiter create(float maxRate){
        return new RequestRateLimiter(Arrays.asList( new RatePeriod(LocalTime.of(0,0,0),
                LocalTime.of(23,59,59), maxRate)));
    }

    /**
     * Create request limiter with ratePeriods calendar - maximum number of requests per second in every period
     * @param ratePeriods - rate calendar
     * @return
     */
    public static RequestRateLimiter create(List<RatePeriod> ratePeriods){
        return new RequestRateLimiter(ratePeriods);
    }

    private void checkArgs(List<RatePeriod> ratePeriods){

        for (RatePeriod rp: ratePeriods ){
            if ( null == rp || rp.maxRate <= 0.0f || null == rp.start || null == rp.end )
                throw new IllegalArgumentException("list contains null or rate is less then zero or period is zero length");
        }
    }

    private float getCurrentRate(){

        LocalTime now = LocalTime.now();

        for (RatePeriod rp: periods){
            if ( now.isAfter( rp.start ) && now.isBefore( rp.end ) )
                return rp.maxRate;
        }

        return Float.MAX_VALUE;
    }



    private RequestRateLimiter(List<RatePeriod> ratePeriods){

        checkArgs(ratePeriods);
        periods = new ArrayList<>(ratePeriods.size());
        periods.addAll(ratePeriods);

        this.minTime = (long)(1000.0f / getCurrentRate());
        this.lastSchedAction = System.currentTimeMillis() - minTime;
    }

    /**
     * Call this method before making actual request.
     * Method call locks until current rate falls down below the limit
     * @throws InterruptedException
     */
    public void consume() throws InterruptedException {

        long timeLeft;

        synchronized(this) {
            long curTime = System.currentTimeMillis();

            minTime = (long)(1000.0f / getCurrentRate());
            timeLeft = lastSchedAction + minTime - curTime;

            long timeSpent = curTime - lastSchedAction + timeLeft;
            avgSpent = (avgSpent + timeSpent) / 2;

            if(timeLeft <= 0) {
                lastSchedAction = curTime;
                return;
            }

            lastSchedAction = curTime + timeLeft;
        }

        Thread.sleep(timeLeft);
    }

    public synchronized float getCuRate(){
        return (float) ( 1000d / avgSpent);
    }
}

Et tests unitaires

import org.junit.Assert;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class RequestRateLimiterTest {


    @Test(expected = IllegalArgumentException.class)
    public void checkSingleThreadZeroRate(){

        // Zero rate
        RequestRateLimiter limiter = RequestRateLimiter.create(0);
        try {
            limiter.consume();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void checkSingleThreadUnlimitedRate(){

        // Unlimited
        RequestRateLimiter limiter = RequestRateLimiter.create(Float.MAX_VALUE);

        long started = System.currentTimeMillis();
        for ( int i = 0; i < 1000; i++ ){

            try {
                limiter.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        long ended = System.currentTimeMillis();
        System.out.println( "Current rate:" + limiter.getCurRate() );
        Assert.assertTrue( ((ended - started) < 1000));
    }

    @Test
    public void rcheckSingleThreadRate(){

        // 3 request per minute
        RequestRateLimiter limiter = RequestRateLimiter.create(3f/60f);

        long started = System.currentTimeMillis();
        for ( int i = 0; i < 3; i++ ){

            try {
                limiter.consume();
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        long ended = System.currentTimeMillis();

        System.out.println( "Current rate:" + limiter.getCurRate() );
        Assert.assertTrue( ((ended - started) >= 60000 ) & ((ended - started) < 61000));
    }



    @Test
    public void checkSingleThreadRateLimit(){

        // 100 request per second
        RequestRateLimiter limiter = RequestRateLimiter.create(100);

        long started = System.currentTimeMillis();
        for ( int i = 0; i < 1000; i++ ){

            try {
                limiter.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        long ended = System.currentTimeMillis();

        System.out.println( "Current rate:" + limiter.getCurRate() );
        Assert.assertTrue( (ended - started) >= ( 10000 - 100 ));
    }

    @Test
    public void checkMultiThreadedRateLimit(){

        // 100 request per second
        RequestRateLimiter limiter = RequestRateLimiter.create(100);
        long started = System.currentTimeMillis();

        List<Future<?>> tasks = new ArrayList<>(10);
        ExecutorService exec = Executors.newFixedThreadPool(10);

        for ( int i = 0; i < 10; i++ ) {

            tasks.add( exec.submit(() -> {
                for (int i1 = 0; i1 < 100; i1++) {

                    try {
                        limiter.consume();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }) );
        }

        tasks.stream().forEach( future -> {
            try {
                future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });

        long ended = System.currentTimeMillis();
        System.out.println( "Current rate:" + limiter.getCurRate() );
        Assert.assertTrue( (ended - started) >= ( 10000 - 100 ) );
    }

    @Test
    public void checkMultiThreaded32RateLimit(){

        // 0,2 request per second
        RequestRateLimiter limiter = RequestRateLimiter.create(0.2f);
        long started = System.currentTimeMillis();

        List<Future<?>> tasks = new ArrayList<>(8);
        ExecutorService exec = Executors.newFixedThreadPool(8);

        for ( int i = 0; i < 8; i++ ) {

            tasks.add( exec.submit(() -> {
                for (int i1 = 0; i1 < 2; i1++) {

                    try {
                        limiter.consume();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }) );
        }

        tasks.stream().forEach( future -> {
            try {
                future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });

        long ended = System.currentTimeMillis();
        System.out.println( "Current rate:" + limiter.getCurRate() );
        Assert.assertTrue( (ended - started) >= ( 10000 - 100 ) );
    }

    @Test
    public void checkMultiThreadedRateLimitDynamicRate(){

        // 100 request per second
        RequestRateLimiter limiter = RequestRateLimiter.create(100);
        long started = System.currentTimeMillis();

        List<Future<?>> tasks = new ArrayList<>(10);
        ExecutorService exec = Executors.newFixedThreadPool(10);

        for ( int i = 0; i < 10; i++ ) {

            tasks.add( exec.submit(() -> {

                Random r = new Random();
                for (int i1 = 0; i1 < 100; i1++) {

                    try {
                        limiter.consume();
                        Thread.sleep(r.nextInt(1000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }) );
        }

        tasks.stream().forEach( future -> {
            try {
                future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });

        long ended = System.currentTimeMillis();
        System.out.println( "Current rate:" + limiter.getCurRate() );
        Assert.assertTrue( (ended - started) >= ( 10000 - 100 ) );
    }

}
Léonid Astakhov
la source
Le code est assez simple. Vous créez simplement le limiteur avec maxRate ou avec périodes et taux. Et puis il suffit d'appeler consommer chaque demande. Chaque fois que le débit n'est pas dépassé, le limiteur revient immédiatement ou attend un certain temps avant de revenir au débit de demande courant inférieur. Il a également une méthode de taux actuel qui renvoie la moyenne glissante du taux actuel.
Leonid Astakhov
0

Ma solution: Une méthode util simple, vous pouvez la modifier pour créer une classe wrapper.

public static Runnable throttle (Runnable realRunner, long delay) {
    Runnable throttleRunner = new Runnable() {
        // whether is waiting to run
        private boolean _isWaiting = false;
        // target time to run realRunner
        private long _timeToRun;
        // specified delay time to wait
        private long _delay = delay;
        // Runnable that has the real task to run
        private Runnable _realRunner = realRunner;
        @Override
        public void run() {
            // current time
            long now;
            synchronized (this) {
                // another thread is waiting, skip
                if (_isWaiting) return;
                now = System.currentTimeMillis();
                // update time to run
                // do not update it each time since
                // you do not want to postpone it unlimited
                _timeToRun = now+_delay;
                // set waiting status
                _isWaiting = true;
            }
            try {
                Thread.sleep(_timeToRun-now);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // clear waiting status before run
                _isWaiting = false;
                // do the real task
                _realRunner.run();
            }
        }};
    return throttleRunner;
}

Prenez de JAVA Thread Debounce et Throttle

benbai123
la source