12.3. カウンター
カウンターは、strong (StrongCounter)またはweak(WeakCounter)になり、いずれも名前で識別されます。各インターフェースには特定のインターフェースがありますが、ロジック(つまり各操作により CompletableFuture が返される)を共有しているため、更新イベントが返され、初期値にリセットできます。
非同期 API を使用しない場合は、sync() メソッドを介して同期カウンターを返すことができます。API は同じですが、CompletableFuture の戻り値はありません。
以下のメソッドは、両方のインターフェースに共通しています。
String getName();
CompletableFuture<Long> getValue();
CompletableFuture<Void> reset();
<T extends CounterListener> Handle<T> addListener(T listener);
CounterConfiguration getConfiguration();
CompletableFuture<Void> remove();
SyncStrongCounter sync(); //SyncWeakCounter for WeakCounter
-
getName()はカウンター名(identifier)を返します。 -
getValue()は現在のカウンターの値を返します。 -
reset()により、カウンターの値を初期値にリセットできます。 -
reset()はリスナーを登録し、更新イベントを受信します。詳細については、「通知およびイベント」セクションをご覧ください。 -
getConfiguration()はカウンターによって使用される設定を返します。 -
remove()はクラスターからカウンター値を削除します。インスタンスは引き続き使用でき、リスナーが保持されます。 -
sync()は同期カウンターを作成します。
削除後にアクセスされると、カウンターは再作成されます。
12.3.1. StrongCounter インターフェース: 一貫性または境界が明確になります。 リンクのコピーリンクがクリップボードにコピーされました!
強力なカウンターでは、Red Hat Data Grid キャッシュに保存されている単一のキーを使用して、必要な整合性を提供します。すべての更新は、その値を更新するためにキーロックの下で実行されます。一方、読み取りはロックを取得し、現在の値を読み取ります。さらに、このスキームではカウンター値をバインドでき、比較および設定/スワップなどのアトミック操作を提供できます。
StrongCounter は、getStrongCounter() メソッドを使用して CounterManager から取得することができます。たとえば、以下のようになります。
CounterManager counterManager = ...
StrongCounter aCounter = counterManager.getStrongCounter("my-counter);
すべての操作は単一のキーに到達するため、StrongCounter は競合レートが高くなります。
StrongCounter インターフェースでは、以下のメソッドを追加します。
default CompletableFuture<Long> incrementAndGet() {
return addAndGet(1L);
}
default CompletableFuture<Long> decrementAndGet() {
return addAndGet(-1L);
}
CompletableFuture<Long> addAndGet(long delta);
CompletableFuture<Boolean> compareAndSet(long expect, long update);
CompletableFuture<Long> compareAndSwap(long expect, long update);
-
incrementAndGet()はカウンターを 1 つずつ増分し、新しい値を返します。 -
decrementAndGet()は、1 つずつカウンターをデクリメントし、新しい値を返します。 -
addAndGet()は、delta をカウンターの値に追加し、新しい値を返します。 -
compareAndSet()およびcompareAndSwap()は、現在の値が想定される場合にカウンターの値を設定します。
CompletableFuture が完了すると、操作は完了とみなされます。
compare-and-set と compare-and-swap の相違点は、操作に成功した場合に、compare-and-set は true を返しますが、compare-and-swap は前の値をか返すことです。戻り値が期待値と同じ場合は、compare-and-swap が正常になります。
12.3.1.1. バインドされた StrongCounter リンクのコピーリンクがクリップボードにコピーされました!
バインドされている場合、上記の更新メソッドはすべて、下限または上限に達すると CounterOutOfBoundsException をスローします。例外には、どちら側にバインドが到達したかを確認するための次のメソッドがあります。
public boolean isUpperBoundReached();
public boolean isLowerBoundReached();
12.3.1.2. ユースケース リンクのコピーリンクがクリップボードにコピーされました!
強力なカウンターは、次の使用例に適しています。
- 各更新後にカウンターの値が必要な場合(例: クラスター単位のIDジェネレーターまたはシーケンス)
- バインドされたカウンターが必要な場合は(例: レートリミッター)
12.3.1.3. 使用例 リンクのコピーリンクがクリップボードにコピーされました!
StrongCounter counter = counterManager.getStrongCounter("unbounded_coutner");
// incrementing the counter
System.out.println("new value is " + counter.incrementAndGet().get());
// decrement the counter's value by 100 using the functional API
counter.addAndGet(-100).thenApply(v -> {
System.out.println("new value is " + v);
return null;
}).get
// alternative, you can do some work while the counter is updated
CompletableFuture<Long> f = counter.addAndGet(10);
// ... do some work ...
System.out.println("new value is " + f.get());
// and then, check the current value
System.out.println("current value is " + counter.getValue().get());
// finally, reset to initial value
counter.reset().get();
System.out.println("current value is " + counter.getValue().get());
// or set to a new value if zero
System.out.println("compare and set succeeded? " + counter.compareAndSet(0, 1));
以下に、バインドされたカウンターを使用する別の例を示します。
StrongCounter counter = counterManager.getStrongCounter("bounded_counter");
// incrementing the counter
try {
System.out.println("new value is " + counter.addAndGet(100).get());
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof CounterOutOfBoundsException) {
if (((CounterOutOfBoundsException) cause).isUpperBoundReached()) {
System.out.println("ops, upper bound reached.");
} else if (((CounterOutOfBoundsException) cause).isLowerBoundReached()) {
System.out.println("ops, lower bound reached.");
}
}
}
// now using the functional API
counter.addAndGet(-100).handle((v, throwable) -> {
if (throwable != null) {
Throwable cause = throwable.getCause();
if (cause instanceof CounterOutOfBoundsException) {
if (((CounterOutOfBoundsException) cause).isUpperBoundReached()) {
System.out.println("ops, upper bound reached.");
} else if (((CounterOutOfBoundsException) cause).isLowerBoundReached()) {
System.out.println("ops, lower bound reached.");
}
}
return null;
}
System.out.println("new value is " + v);
return null;
}).get();
Compare-and-set と Compare-and-swap の比較例:
StrongCounter counter = counterManager.getStrongCounter("my-counter");
long oldValue, newValue;
do {
oldValue = counter.getValue().get();
newValue = someLogic(oldValue);
} while (!counter.compareAndSet(oldValue, newValue).get());
compare-and-swap では、呼び出しカウンターの呼び出し (counter.getValue()) が 1 つ保存されます。
StrongCounter counter = counterManager.getStrongCounter("my-counter");
long oldValue = counter.getValue().get();
long currentValue, newValue;
do {
currentValue = oldValue;
newValue = someLogic(oldValue);
} while ((oldValue = counter.compareAndSwap(oldValue, newValue).get()) != currentValue);