Java“有序”信号灯

时间:2018-11-02 19:10:10

标签: java concurrency synchronization semaphore

对于如何解决它,我有一个问题和一个模糊的主意,但是我会尽量避免在上下文中过度使用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上的方法。

3 个答案:

答案 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