N 초 내에 M 요청에 대한 제한 메소드 호출


137

N 초 안에 최대 M 호출까지 일부 메소드 실행을 조절하는 구성 요소 / 클래스가 필요합니다 (또는 ms 또는 nano는 중요하지 않습니다).

즉, N 초 슬라이딩 창에서 내 메소드가 M 번 이상 실행되지 않아야합니다.

기존 수업을 모르는 경우 솔루션 / 아이디어를 구현하는 방법을 자유롭게 게시하십시오.



3
에서이 문제에 대한 몇 가지 좋은 답변이 있습니다 stackoverflow.com/questions/667508/...
skaffman

> N 초 슬라이딩 창에서 내 메소드가 M 번 이상 실행되는지 확인해야합니다. 최근에 .NET 에서이 작업을 수행하는 방법에 대한 블로그 게시물을 작성했습니다. Java에서 비슷한 것을 만들 수 있습니다. .NET에서 더 나은 속도 제한
Jack Leitch

원래 질문은이 블로그 게시물에서 해결 된 문제와 비슷하게 들립니다. [Java Multi-Channel Asynchronous Throttler] ( cordinc.com/blog/2010/04/java-multichannel-asynchronous.html ). M의 비율이 N 초 호출의 경우, throttler는 것을이 블로그 보장에서 논의 된 모든 타임 라인에 길이 N의 간격이 M 통화보다 더 많은 포함되지 않습니다.
Hbf

답변:


81

고정 크기 M의 타임 스탬프 링 버퍼 를 사용합니다 . 메서드가 호출 될 때마다 가장 오래된 항목을 확인하고 과거에 N 초 미만인 경우 다른 항목을 실행하고 추가합니다. 시차.


4
아름다운. 내가 필요한 것만 빠른 시도는이 라인을 구현하고 최소 메모리 공간을 구현하기 위해 ~ 10 줄을 보여줍니다. 스레드 안전성과 수신 요청 큐잉에 대해 생각하면됩니다.
vtrubnikov

5
이것이 java.util.concurrent의 DelayQueue를 사용하는 이유입니다. 여러 스레드가 동일한 항목에서 작동하는 문제를 방지합니다.
erickson

5
멀티 스레드 케이스의 경우 토큰 버킷 접근 방식이 더 나은 선택 일 수 있습니다.
Michael Borgwardt

1
이 알고리즘의 이름이 전혀 없다면이 알고리즘이 어떻게 호출되는지 알고 있습니까?
Vlado Pandžić

80

나를 위해 즉시 사용 된 것은 Google Guava RateLimiter 입니다.

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

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

19
Guava RateLimiter가 스레드를 차단하고 스레드 풀을 쉽게 소모 하므로이 솔루션을 권장하지 않습니다.
kaviddiss

18
@kaviddiss 차단하고 싶지 않다면tryAquire()
slf

7
RateLimiter의 현재 구현 (적어도 나를 위해)의 문제는 1 초보다 긴 시간을 허용하지 않으므로 분당 1의 속도를 허용한다는 것입니다.
John B

4
I는 이해 @ 존 B로서 멀리하면 RateLimiter.create (60.0) + rateLimiter.acquire (60)를 사용하여 RateLimiter 분당 1 개 요구를 달성 할 수있다
divideByZero

2
@radiantRazor Ratelimiter.create (1.0 / 60) 및 acquire ()는 분당 1 회의 호출을 달성합니다.
bizentass

30

구체적으로,을 사용하여이를 구현할 수 있어야합니다 DelayQueue. M Delayed지연 시간이 0으로 설정된 인스턴스를 사용 하여 큐를 초기화하십시오 . 메소드에 대한 요청이 들어 오면 take토큰은 조절 요구 사항이 충족 될 때까지 메소드를 차단합니다. 토큰을 가져 오면 add지연 시간이 대기열에있는 새 토큰입니다 N.


1
예, 이것은 트릭을 수행합니다. 그러나 DelayQueue는 우선 순위 대기열을 통해 균형 잡힌 이진 해시 (많은 비교 offer와 가능한 배열 성장 을 의미 함)를 사용하고 있기 때문에 특히 마음에 들지 않습니다 . 다른 사람들에게는 이것이 완벽하게 맞는 것 같습니다.
vtrubnikov

5
실제로이 애플리케이션에서 힙에 추가 된 새 요소는 거의 항상 힙에서 최대 요소 (즉, 가장 긴 지연 시간)이므로 일반적으로 추가 당 하나의 비교가 필요합니다. 또한 하나의 요소가 하나의 요소를 가져온 후에 만 ​​추가되므로 알고리즘이 올바르게 구현되면 배열이 커지지 않습니다.
erickson

3
크기 M을 유지하고 N을 상대적으로 작은 밀리 초 단위로 작게하여 요청이 큰 버스트에서 발생하는 것을 원하지 않는 경우에도 도움이됩니다. 예. M = 5, N = 20ms는 5의 크기로 발생하는 250 / sec 케핑 버스트의 스루풋을 제공합니다.
FUD

동시 요청이 허용되는 경우 백만 rpm으로 확장됩니까? 백만 개의 지연 요소를 추가해야합니다. 또한 여러 스레드가 poll ()을 호출하고 매번 잠길 때 대기 시간이 길어질 수 있습니다.
Aditya Joshee

@AdityaJoshee 나는 그것을 벤치마킹하지 않았지만 시간이 걸리면 오버 헤드를 감지하려고 노력할 것입니다. 한 가지 주목할 점은 1 초 후에 만료되는 백만 개의 토큰이 필요하지 않다는 것입니다. 10 밀리 초에 만료되는 100 개의 토큰, 밀리 초에 만료되는 10 개의 토큰 등을 가질 수 있습니다. 이는 실제로 순간 속도가 평균 속도에 가까워지면서 스파이크를 부드럽게하여 클라이언트에서 백업을 유발할 수 있지만 자연스러운 결과입니다. 속도 제한의. 백만 RPM은 조절처럼 들리지 않습니다. 유스 케이스를 설명 할 수 있다면 더 나은 아이디어가있을 수 있습니다.
erickson

21

토큰 버킷 알고리즘을 읽으십시오 . 기본적으로 토큰이 들어있는 버킷이 있습니다. 메소드를 실행할 때마다 토큰을 가져옵니다. 더 이상 토큰이 없으면 토큰을 얻을 때까지 차단합니다. 한편, 일정한 간격으로 토큰을 보충하는 외부 행위자가 있습니다.

이 작업을 수행하는 라이브러리를 알지 못합니다. 이 논리를 코드에 쓰거나 AspectJ를 사용하여 동작을 추가 할 수 있습니다.


3
제안, 재미있는 algo 주셔서 감사합니다. 그러나 정확히 내가 필요한 것은 아닙니다. 예를 들어, 실행을 초당 5 번으로 제한해야합니다. 토큰 버킷을 사용하고 동시에 10 개의 요청이 들어 오면 처음 5 개의 통화가 사용 가능한 모든 토큰을 가져와 잠시 실행하는 반면 나머지 5 개의 통화는 1/5 초의 고정 된 간격으로 실행됩니다. 이러한 상황에서 나는 1 초가 지나야 단일 버스트에서 5 번의 호출을 실행해야합니다.
vtrubnikov

5
1/5 초마다 1이 아닌 1 초마다 5 개의 토큰을 버킷에 추가하면 어떻게 되나요?
Kevin

@Kevin 아니 이것은 여전히 ​​나에게 '슬라이딩 창'효과를주지 않을 것입니다
vtrubnikov

2
@valery 그렇습니다. (하지만 M의 토큰 한도를 기억하십시오)
nos

"외부 배우"가 필요하지 않습니다. 요청 시간에 대해 메타 데이터를 유지하면 모든 작업을 단일 스레드로 수행 할 수 있습니다.
Marsellus Wallace 2016 년

8

분산 시스템에서 작동하는 Java 기반 슬라이딩 윈도우 속도 제한 기가 필요한 경우 https://github.com/mokies/ratelimitj 프로젝트를 살펴볼 수 있습니다 .

요청을 분당 50 개로 제한하는 Redis 지원 구성은 다음과 같습니다.

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 구성에 대한 자세한 내용 은 https://github.com/mokies/ratelimitj/tree/master/ratelimitj-redis를 참조 하십시오 .


5

응용 프로그램에 따라 다릅니다.

하는 경우 상상 여러 스레드가 몇 가지 할 수있는 토큰을 원하는 전 세계적으로 속도 - 제한 조치허용 버스트 없음을 즉, 10 초 당 10 개 조치를 제한 할 (하지만 당신은 10 개 행동이 첫 번째, 두 번째에서 일하고자 다음 남아 있지 않습니다 9 초 중지).

DelayedQueue의 단점은 스레드 요청 토큰이 요청을 이행하는 순서가 아닐 수도 있다는 단점이 있습니다. 토큰을 기다리는 여러 스레드가 차단 된 경우 다음 스레드를 사용할 스레드가 확실하지 않습니다. 내 견해로는 스레드가 영원히 대기 할 수도 있습니다.

한 가지 해결책은 두 개의 연속 작업 사이에 최소 시간 간격을두고 요청한 순서와 동일한 순서로 작업을 수행하는 것입니다.

구현은 다음과 같습니다.

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

minTime여기서 무엇을 의미합니까? 무엇을합니까? 그것에 대해 설명 할 수 있습니까?
플래시

minTime다음 토큰을 사용하기 전에 토큰을 사용한 후 통과해야하는 최소 시간입니다.
Duarte Meneses


2

간단한 스로틀 링 알고리즘을 구현했습니다. http://krishnaprasadas.blogspot.in/2012/05/throttling-algorithm.html 링크를 사용해보십시오

알고리즘에 대한 간략한 설명

이 알고리즘은 Java Delayed Queue 기능을 활용합니다 . 예상되는 지연 시간 (밀리 초 TimeUnit의 경우 1000 / M) 으로 지연된 오브젝트를 작성 하십시오 . 동일한 객체를 지연 대기열에 넣으면 인턴이 이동 창을 제공합니다. 그런 다음 각 메소드 호출 대기열에서 객체를 가져 오기 전에 지정된 지연 후에 만 ​​반환되는 차단 호출이며 메소드 호출 후에 업데이트 된 시간 (여기서는 현재 밀리 초)으로 객체를 대기열에 넣는 것을 잊지 마십시오 .

여기서 지연이 다른 여러 지연된 객체를 가질 수도 있습니다. 이 방법은 또한 높은 처리량을 제공합니다.


6
알고리즘 요약을 게시해야합니다. 링크가 사라지면 답이 쓸모 없게됩니다.
jwr

고마워요, 요약을 추가했습니다.
Krishas

1

아래의 구현은 임의의 요청 시간 정밀도를 처리 할 수 ​​있으며 각 요청에 대해 O (1) 시간 복잡성을 가지며 O (1) 공간 복잡성과 같은 추가 버퍼가 필요하지 않으며 대신 토큰을 해제하는 데 백그라운드 스레드가 필요하지 않습니다. 토큰은 마지막 요청 이후 경과 된 시간에 따라 해제됩니다.

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

이 간단한 접근법을 사용하십시오.

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

위의 LeakyBucket 코드에 대한 업데이트입니다. 이것은 초당 1000 개 이상의 요청에 대해 작동합니다.

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

위의 단위 테스트 :

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

minTimeNano여기에 무슨 뜻입니까? 설명 할 수 있습니까?
플래시

0

간단한 속도 제한 기의 고급 버전입니다.

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

그리고 단위 테스트

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

}

코드는 매우 간단합니다. maxRate 또는 기간 및 비율로 리미터를 작성하기 만하면됩니다. 그런 다음 전화는 모든 요청을 소비합니다. 속도가 초과되지 않을 때마다 리미터는 즉시 반환되거나 시간을 기다렸다가 더 낮은 현재 요청 속도로 돌아갑니다. 또한 현재 속도의 슬라이딩 평균을 반환하는 현재 속도 방법이 있습니다.
Leonid Astakhov

0

내 솔루션 : 간단한 util 메소드로 래퍼 클래스를 만들도록 수정할 수 있습니다.

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

자바 스레드 디 바운스 및 스로틀 에서 가져 오기

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.