0

私はクラスシリアライザーを持っています:

class Serializer<T> extends Consumer<T> {
    final Consumer<? super T> actual;
    // constructor omitted for brewity
    @Override public synchronized void accept(T t) {
         actual.accept(t);
    }
}

目的は、actual が一度に 1 つのスレッドから実行されるようにすることです。ただし、ロックを保持している間にコールバックを呼び出すことは一般的に危険であるため、ロックを保持する代わりに、呼び出し元が着信値をキューに入れると、スレッドの 1 つが入り、キューを空にし、実際のコンシューマーをループで順番に呼び出します。(その他の制限は、同時呼び出し元の数がわからないことです。)

    final ConcurrentLinkedQueue<T> queue;
    final AtomicInteger wip;
    @Override public void accept(T t) {
        queue.offer(t);
        if (wip.getAndIncrement() == 0) {
            do {
                actual.accept(queue.poll());
            } while (wip.decrementAndGet() > 0);
        }
    }

これは機能し、無制限のキュー、スレッド ホッピング、ループ内でスタックするスレッドの問題を回避しますが、ベンチマークでは、直接メソッド呼び出しと比較して、シングル スレッドのケースで 10% のスループットが得られます。I. 同期ブロックを使用してこのキューイング/発行を実装すると、JVM が同期化を最適化しないため、ベンチマークは直接ケースの 50% を示します。これは素晴らしいことですが、スケールも大きくありません。juc.Lock を使用するとスケーリングしますが、上記のコードと同様にシングルスレッドのスループットが低下します。私が間違っていなければ、JVM が最適化されて同期化された後でも、メソッドが再び同時に呼び出されてロックが元に戻された場合に備えて、何らかのガードを使用する必要があります。

私の質問は、ロック、キュー、またはその他のシリアル化ロジックで同様の効果を達成するにはどうすればよいかということです。コードはスケーリングされ、シングル スレッドでの使用でも高速のままです。

4

2 に答える 2

1

他の人が言ったように、synchronizedすでにかなり効率的なツールを提供しています。したがって、より良いパフォーマンスを期待して、別のものを使用することを動機付けるべきではありません。

ただし、質問で述べたように発信者をブロックすることが意図されていない場合、それは正当です。(これは、パフォーマンスを低下させて生活することを意味する場合がありますが)。

キューとアトミック整数を使用した試みを見て最初に目にするのは、保留中のアイテムがなく、他の消費者が実行されていない場合にキューをバイパスできることです。競合が少ない場合は、オーバーヘッドが削減される可能性があります。

final ConcurrentLinkedQueue<T> queue;
final AtomicInteger wip;
@Override public void accept(T t) {
  if(wip.compareAndSet(0, 1)) { // no contention?
    actual.accept(t);
    if(wip.decrementAndGet()==0) return; // still no contention
  }
  else {
    if(!queue.offer(t))
      throw new AssertionError("queue should be unbounded");
    if(wip.getAndIncrement() != 0) return; // other consumer running
  }
  do {
    actual.accept(queue.poll());
  } while (wip.decrementAndGet() > 0);
}
于 2014-05-19T10:20:46.717 に答える