提问者:小点点

同步java问题


我正在尝试实现令牌桶速率限制器。下面是我编写的基本代码,但没有得到预期的结果。似乎我遗漏了一些东西,或者我还没有完全理解如何在java中线程函数。有人能帮助我寻找正确的方向吗?

public class RateLimiterTokenBucket implements RateLimiter, Runnable{

    Integer capacity;
    Integer frequencyInMilliseconds;
    Integer currentCapacity;

    public RateLimiterTokenBucket(Integer frequencyInMilliseconds,Integer capacity) {
        this.frequencyInMilliseconds = frequencyInMilliseconds;
        this.capacity = capacity;
        this.currentCapacity = capacity;
    }

    @Override
    public boolean consume() {
        synchronized (currentCapacity) {
            if(currentCapacity <= 0) return false;
            --currentCapacity;
            return true;
        }
    }

    @Override
    public void run() {
        synchronized (currentCapacity) {
            OffsetDateTime t = OffsetDateTime.now();
            while(true) {
                if(OffsetDateTime.now().isAfter(t.plus(frequencyInMilliseconds, ChronoUnit.MILLIS))){
                    if(currentCapacity<capacity){
                        System.out.println("token added");
                        ++currentCapacity;
                    }
                    t=OffsetDateTime.now();
                }
            }
        }
    }
}

主类

public static void main(String[] args) {
        RateLimiter rl = new RateLimiterTokenBucket(10,5);
        Thread t = new Thread((Runnable) rl);
        t.start();
        while(true){
            if(rl.consume()) {
                System.out.println(OffsetDateTime.now()+" ---> true");
            }
        }
    }

只是得到

2023-04-29T19:30:54.685553+05:30 ---> true
token added

作为两个线程main和t1仍在运行的输出


共1个答案

匿名用户

您的run方法的整个主体是一个同步的块。这实际上总是一个错误。在线程完全完成并且run方法返回之前,您不希望任何其他线程能够执行什么操作?

此外,同步(当前容量)看起来是一个错误,因为您的程序将不同的对象分配给当前容量。当一个线程在Integer(5)上同步而另一个线程在Integer(4)上同步时意味着什么?如果您从不编写同步(foo),您就可以避免犯这个错误,除非foo是一个最终变量。

另外值得注意的是:您的主线程在等待下一个令牌时旋转(即,它CPU时间燃烧)。这并不总是一件坏事,但如果您正在运行的主机没有为该线程独占使用保留的CPU,并且有其他线程已准备好运行并等待轮到它们使用CPU,则通常是一件坏事。

P. S.,您是否考虑过使用Semaphore作为“桶”?您的速率限制器可以调用semaphore.有多少令牌在桶中,并调用…release()向桶中添加另一个令牌。并且,您的主线程可以调用semaphore.获取()来等待下一个令牌。