对于如何解决它,我有一个问题和一个模糊的主意,但是我会尽量避免在上下文中过度使用XY problem。
我有一个异步方法,该方法立即返回一个guava ListenableFuture
,我需要调用数十万或数百万次(将来本身可能需要一点时间才能完成)。我真的不能更改该方法的内部。内部涉及一些严重的资源争用,因此我想限制一次发生的对该方法的调用次数。所以我尝试使用Semaphore
:
public class ConcurrentCallsLimiter<In, Out> {
private final Function<In, ListenableFuture<Out>> fn;
private final Semaphore semaphore;
private final Executor releasingExecutor;
public ConcurrentCallsLimiter(
int limit, Executor releasingExecutor,
Function<In, ListenableFuture<Out>> fn) {
this.semaphore = new Semaphore(limit);
this.fn = fn;
this.releasingExecutor = releasingExecutor;
}
public ListenableFuture<Out> apply(In in) throws InterruptedException {
semaphore.acquire();
ListenableFuture<Out> result = fn.apply(in);
result.addListener(() -> semaphore.release(), releasingExecutor);
return result;
}
}
因此,我可以将我的呼叫包装在此类中,然后调用该呼叫:
ConcurrentLimiter<Foo, Bar> cl =
new ConcurrentLimiter(10, executor, someService::turnFooIntoBar);
for (Foo foo : foos) {
ListenableFuture<Bar> bar = cl.apply(foo);
// handle bar (no pun intended)
}
这种种的作品。问题在于尾部延迟确实很差。一些调用变得“不幸”,并最终花费很长时间尝试在该方法调用中获取资源。某些内部指数回退逻辑使这种情况更加恶化,与较急于等待更短时间再尝试的新呼叫相比,不幸的呼叫获得所需资源的机会越来越少。
修复该问题的理想方法是,如果有类似该信号量但具有顺序概念的东西。例如,如果限制为10,则当前的第11个呼叫必须等待前10个呼叫中的 any 完成。我想要的是第11个调用必须等待 first 调用完成。这样,“不幸”的电话就不会继续被不断涌入的新电话所困扰。
似乎我可以为调用分配一个整数序列号,并以某种方式跟踪尚未完成的最低序列号,但还不太清楚如何使之工作,特别是因为实际上没有任何有用的“等待” AtomicInteger
上的方法。
答案 0 :(得分:0)
您可以使用公平参数创建信号量:
Semaphore(int permits, boolean fair)
// fair-如果此信号量将确保在争用情况下先入先出许可,则为true,否则为false
答案 1 :(得分:0)
我想到的一个不理想的解决方案是使用Queue
存储所有期货:
public class ConcurrentCallsLimiter<In, Out> {
private final Function<In, ListenableFuture<Out>> fn;
private final int limit;
private final Queue<ListenableFuture<Out>> queue = new ArrayDeque<>();
public ConcurrentCallsLimiter(int limit, Function<In, ListenableFuture<Out>> fn) {
this.limit = limit;
this.fn = fn;
}
public ListenableFuture<Out> apply(In in) throws InterruptedException {
if (queue.size() == limit) {
queue.remove().get();
}
ListenableFuture<Out> result = fn.apply(in);
queue.add(result);
return result;
}
}
但是,这似乎是对内存的极大浪费。涉及的对象可能有点大,并且可以将限制设置得很高。因此,我对没有O(n)内存使用情况的更好答案持开放态度。似乎在O(1)中应该可行。
答案 2 :(得分:0)
“如果存在类似于该信号量但具有顺序概念的情况。”
存在这样的野兽。它称为Blocking queue
。创建这样的队列,在其中放置10个项目,然后使用take
代替acquire
,并使用put
代替release
。