Kısma yöntemi, M isteklerini N saniye içinde çağırır


137

N saniye (veya ms veya nanos, önemli değil) maksimum M çağrıları için bazı yöntemin yürütülmesini hızlandıran bir bileşen / sınıf gerekir.

Başka bir deyişle, yöntemimin N saniyeden oluşan bir sürgülü pencerede en fazla M kez yürütüldüğünden emin olmalıyım.

Mevcut sınıfı bilmiyorsanız, çözümlerinizi / fikirlerinizi bunu nasıl uygulayacağınızı yayınlamaktan çekinmeyin.




> Yöntemin N saniyelik bir sürgülü pencerede> en fazla M kez yürütüldüğünden emin olmalıyım. Geçenlerde .NET'te bunun nasıl yapılacağı hakkında bir blog yazısı yazdım. Java'da benzer bir şey oluşturabilirsiniz. .NET'te Daha İyi Fiyat Sınırlaması
Jack Leitch

Orijinal soru bu blog yazısında çözülen soruna çok benziyor: [Java Çok Kanallı Asenkron Throttler] ( cordinc.com/blog/2010/04/java-multichannel-asynchronous.html ). M'nin bir oran K saniyede aramalar için, throttler bu Blog garantiler tartışılan herhangi bir zaman çizelgesi uzunluk N aralığı M çağrıları daha içermez.
Hbf

Yanıtlar:


81

Ben kullanmak istiyorum halka tampon size aksi takdirde eski giriş kontrol ve daha az N saniyeden daha geçmişte kaldı, sen yürütmek ve başka giriş eklemek, M. sabit boyutu ile uyku yöntemi denir her zaman zaman damgaları zaman farkı için.


4
Güzel. Tam ihtiyacım olan şey. Hızlı denemeler bunu uygulamak için ~ 10 satır ve minimum bellek kapladığı alanı gösterir. Sadece iş parçacığı güvenliği ve gelen isteklerin kuyruğa alınması düşünmek gerekir.
vtrubnikov

5
Bu yüzden java.util.concurrent'dan DelayQueue kullanıyorsunuz. Aynı girişe etki eden birden çok iş parçacığı sorununu önler.
erickson

5
Çok iş parçacıklı bir durum için token kova yaklaşımı daha iyi bir seçim olabilir diye düşünüyorum.
Michael Borgwardt

1
Herhangi bir adı varsa bu algoritmanın nasıl adlandırıldığını biliyor musunuz?
Vlado Pandžić

80

Benim için kutudan çıkan şey Google Guava RateLimiter'di .

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

private void someMethod() {
    throttle.acquire();
    // Do something
}

19
Guava RateLimiter iş parçacığını engelleyecek ve bu iş parçacığı havuzunu kolayca tüketeceği için bu çözümü tavsiye etmem.
kavitasyon

18
@kaviddiss bloke etmek istemiyorsanız o zaman kullanıntryAquire()
slf

7
Şu anda RateLimiter (en azından benim için) uygulamasıyla ilgili sorun, 1 saniyeden daha uzun zaman periyotlarına izin vermemesidir ve bu nedenle örneğin dakikada 1 oranındadır.
John B

4
@John B Anladığım kadarıyla RateLimiter ile RateLimiter.create (60.0) + rateLimiter.acquire (60)
divideByZero 18:15 saat

2
@radiantRazor Ratelimiter.create (1.0 / 60) ve acquire () dakikada 1 çağrı yapar.
bizentass

30

Somut terimlerle, bunu a DelayQueue. Sırayı M Delayed, gecikmeleri başlangıçta sıfıra ayarlanmış durumlarla başlatın . Yönteme yönelik istek geldikçe take, kısma gereksinimi karşılanıncaya kadar yöntemin engellenmesine neden olan bir belirteç. Bir belirteç alındığında, addsıraya gecikmeli yeni bir belirteç N.


1
Evet, bu işe yarayacaktır. Ama özellikle DelayQueue sevmiyorum çünkü (PriortyQueue aracılığıyla) dengeli bir ikili karma (yani karşılaştırma offerve olası dizi büyümesi anlamına gelir ) ve hepsi benim için ağır. Sanırım diğerleri için bu gayet iyi olabilir.
vtrubnikov

5
Aslında, bu uygulamada, öbeğe eklenen yeni öğe neredeyse her zaman öbekteki maksimum öğe olacağından (yani, en uzun gecikmeye sahip), genellikle ekleme başına bir karşılaştırma gerekir. Ayrıca, algoritma doğru şekilde uygulanırsa dizi asla büyümeyecektir, çünkü bir öğe yalnızca bir öğe alındıktan sonra eklenir.
erickson

3
Bunu, M boyutunu ve N'yi avuç milis sırasına göre nispeten küçük tutarak büyük patlamalarda isteklerin olmasını istemediğiniz durumlarda da yararlı buldum. Örneğin. M = 5, N = 20 ms, 5 boyutunda 250 / sn kepping patlaması geçirme süresi sağlayacaktır
FUD

Bu bir milyon rpm için ölçeklendirilecek mi ve eşzamanlı taleplere ne zaman izin verilecek? Bir milyon gecikmeli öğe eklemem gerekecek. Ayrıca köşe vakaları gecikmede yüksek olacaktır - birden çok iş parçacığının anket () çağırdığı ve her seferinde kilitlendiği durumda.
Aditya Joshee

@AdityaJoshee Kıyaslama yapmadım, ama biraz zaman alırsam yükü anlamaya çalışacağım. Dikkat edilmesi gereken bir şey, 1 saniyede sona erecek 1 milyon jetona ihtiyacınız olmamasıdır. 10 milisaniyede sona eren 100 jeton, milisaniyede sona eren 10 jeton, vb. hız sınırlaması. Yine de 1 milyon devir / dakika, neredeyse kısma gibi geliyor. Kullanım durumunuzu açıklayabilirseniz daha iyi fikirlerim olabilir.
erickson

21

Token kova algoritmasını okuyun . Temel olarak, içinde jeton bulunan bir kova var. Yöntemi her çalıştırdığınızda bir jeton alırsınız. Başka simge yoksa, bir tane alana kadar engellersiniz. Bu arada, belirteçleri sabit bir aralıkta dolduran bazı dış aktörler var.

Bunu (ya da benzer bir şeyi) yapmak için bir kütüphane farkında değilim. Bu mantığı kodunuza yazabilir veya davranışı eklemek için AspectJ kullanabilirsiniz.


3
Öneri için teşekkürler, ilginç algo. Ama tam olarak ihtiyacım olan şey değil. Örneğin, yürütmeyi saniyede 5 çağrı ile sınırlandırmam gerekiyor. Jeton kovasını kullanırsam ve aynı anda 10 istek gelirse, ilk 5 çağrı kullanılabilir tüm belirteçleri alır ve anlık olarak yürütülürken, kalan 5 çağrı 1/5 sn sabit aralıklarla yürütülür. Bu durumda, sadece 1 saniye geçtikten sonra tek bir seri çekim için 5 çağrı kalmam gerekiyor.
vtrubnikov

5
Kepçeye her 1/5 saniyede 1 yerine saniyede 5 jeton (veya 5 - (5 kalan)) eklediyseniz ne olur?
Kevin

@Kevin hayır bu hala bana 'sürgülü pencere' etkisi
vermeyecek

2
@ değer evet. (M'deki jetonları kapatmayı unutmayın)
nos

"harici aktöre" gerek yok. Meta verileri yaklaşık istek sürelerinde tutarsanız her şey tek iş parçacıklı yapılabilir.
Marsellus Wallace

8

Dağıtılmış bir sistemde çalışacak Java tabanlı kayan pencere hızı sınırlayıcısına ihtiyacınız varsa, https://github.com/mokies/ratelimitj projesine göz atmak isteyebilirsiniz .

IP ile istekleri dakikada 50 ile sınırlamak için Redis destekli bir yapılandırma şöyle görünür:

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");

Redis yapılandırması hakkında ayrıntılı bilgi için https://github.com/mokies/ratelimitj/tree/master/ratelimitj-redis adresine bakın .


5

Bu uygulamaya bağlıdır.

Hangi durumda hayal Birden çok iş parçacığı bazı yapmak için bir belirteç istediğiniz global hız sınırlı eylemi ile izin kaymalarının hayır yani 10 saniye başına 10 eylemleri sınırlamak istiyorsanız (ama 10 eylemleri ilk saniyede gerçekleşmesini istiyor ve ardından kalmaz 9 saniye durdu).

DelayedQueue'nin bir dezavantajı vardır: iş parçacığı istek belirteçlerinin hangi sırada isteklerini yerine getirdikleri sırası olmayabilir. Bir simgeyi beklerken birden fazla iş parçacığı engellenirse, hangisinin bir sonraki kullanılabilir simgeyi alacağı net değildir. Benim görüşüme göre sonsuza kadar bekleyen iplikler bile olabilir.

Bir çözüm, birbirini takip eden iki eylem arasında minimum zaman aralığına sahip olmak ve eylemleri talep edilen sırayla gerçekleştirmektir.

İşte bir uygulama:

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

minTimeburada ne anlama geliyor? Bu ne işe yarıyor? Bunu açıklayabilir misin?
flaş

minTimebir belirteç tüketildikten sonra bir sonraki belirteç tüketilmeden önce geçmesi gereken minimum süredir.
Duarte Meneses

3

Sorduğunuz şey olmasa da ThreadPoolExecutor, N saniye içinde M istekleri yerine M eşzamanlı isteklerini kapsayacak şekilde tasarlanmış olan da yararlı olabilir.


2

Basit bir azaltma algoritması uyguladım. Bu bağlantıyı deneyin, http://krishnaprasadas.blogspot.in/2012/05/throttling-algorithm.html

Algoritma hakkında kısa bir bilgi,

Bu algoritma Java Gecikmeli Kuyruk özelliğini kullanır . Beklenen gecikmeyle gecikmeli bir nesne oluşturun (burada milisaniye TimeUnit için 1000 / M ). Aynı nesneyi stajyerin bizim için hareketli penceresini sağlayacak olan gecikmiş kuyruğa koyun. Daha sonra her yöntem çağrısından önce almak nesne kuyruk oluşturur, almak sadece belirtilen gecikme sonrasında dönecektir bir engelleme çağrısı olduğunu ve yöntem çağrısından sonra güncelleme saati sıraya nesne koymak unutmayın (burada akım milisaniye) .

Burada farklı gecikmeli birden çok gecikmeli nesnemiz olabilir. Bu yaklaşım aynı zamanda yüksek verim sağlayacaktır.


6
Algoritmanızın bir özetini göndermelisiniz. Bağlantınız koparsa cevabınız işe yaramaz hale gelir.
jwr

Teşekkürler, özeti ekledim.
Krishas

1

Aşağıdaki uygulamam isteğe bağlı istek zaman hassasiyetini işleyebilir, her istek için O (1) zaman karmaşıklığına sahiptir, ek bir tampon gerektirmez, örneğin O (1) alan karmaşıklığı, ayrıca jetonu serbest bırakmak için arka plan iş parçacığı gerektirmez jetonlar son istekten bu yana geçen süreye göre serbest bırakılır.

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

0

Bu basit yaklaşımı kullanmaya çalışın:

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

}



0

Bu, yukarıdaki LeakyBucket kodunun bir güncellemesidir. Bu, saniyede 1000'den fazla istek için çalışır.

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

ve yukarıdaki testler:

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

minTimeNanoburada ne anlama geliyor? açıklayabilir misin?
flaş

0

İşte basit oran sınırlayıcısının biraz gelişmiş versiyonu

/**
 * 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);
    }
}

Ve birim testleri

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

}

Kod oldukça basit. Sınırlayıcıyı maxRate veya nokta ve oran ile oluşturursunuz. Ve sonra sadece her isteği tüketmek çağırın. Oran aşılmadığında, sınırlayıcı hemen geri döner veya daha düşük akım talep oranına dönmeden önce bir süre bekler. Ayrıca, geçerli oranın kayma ortalamasını döndüren akım hızı yöntemi vardır.
Leonid Astakhov

0

Benim çözümüm: Basit bir util yöntemi, bir sarıcı sınıf oluşturmak için değiştirebilirsiniz.

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

Dan al JAVA Konu debounce ve Throttle

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.