Remote Counters
What You Will Learn
How to create and use distributed counters: bounded strong counters with upper limits, unbounded strong counters with compare-and-set operations, and weak counters optimized for fast increments.
Prerequisites
-
Java 17+
-
An Infinispan Server running on
localhost:11222(or Docker/Podman available for Testcontainers)
Step 1: Connect and Create a Bounded Strong Counter
Connect to the server, obtain a CounterManager, and define a counter with an upper bound. Attempts to exceed the bound throw an exception:
// Connect to the server and obtain the CounterManager
cacheManager = TutorialsConnectorHelper.connect();
counterManager = RemoteCounterManagerFactory.asCounterManager(cacheManager);
// Create a bounded strong counter with an upper bound of 10
counterManager.defineCounter("counter-1", CounterConfiguration.builder(CounterType.BOUNDED_STRONG)
.upperBound(10)
.initialValue(1)
.build());
// StrongCounter provides the higher consistency. Its value is known during the increment/decrement and it may be bounded.
// Bounded counters are aimed for uses cases where a limit is needed.
counter1 = counterManager.getStrongCounter("counter-1");
// All methods returns a CompletableFuture. So you can do other work while the counter value is being computed.
counter1.getValue().thenAccept(value -> System.out.println("Counter-1 initial value is " + value)).get();
Step 2: Create an Unbounded Strong Counter
Unbounded strong counters support compare-and-set, listeners, and reset:
// Similar to counter-1, counter-2 is a strong counter but it is unbounded. It will never throw the CounterOutOfBoundsException
counterManager.defineCounter("counter-2", CounterConfiguration.builder(CounterType.UNBOUNDED_STRONG)
.initialValue(2)
.build());
counter2 = counterManager.getStrongCounter("counter-2");
// All counters allow a listener to be registered.
// The handle can be used to remove the listener
counter2.addListener(event -> System.out
.println("Counter-2 event: oldValue=" + event.getOldValue() + " newValue=" + event.getNewValue()));
// Adding MAX_VALUE won't throws an exception. But the all the increments won't have any effect since we can store
//any value larger the MAX_VALUE
counter2.addAndGet(Long.MAX_VALUE).thenAccept(aLong -> System.out.println("Counter-2 value is " + aLong)).get();
// Conditional operations are allowed in strong counters
counter2.compareAndSet(Long.MAX_VALUE, 0)
.thenAccept(aBoolean -> System.out.println("Counter-2 CAS result is " + aBoolean)).get();
counter2.getValue().thenAccept(value -> System.out.println("Counter-2 value is " + value)).get();
// Reset the counter to its initial value (2)
counter2.reset().get();
counter2.getValue().thenAccept(value -> System.out.println("Counter-2 initial value is " + value)).get();
Step 3: Create a Weak Counter
Weak counters trade consistency for speed. Their value is computed lazily:
// And finally, the third counter is a weak counter.
counterManager.defineCounter("counter-3", CounterConfiguration.builder(CounterType.WEAK)
.initialValue(3)
.build());
// Retrieve counter-3
counter3 = counterManager.getWeakCounter("counter-3");
// Weak counter doesn't have its value available during updates. This makes the increment faster than the StrongCounter
// Its value is computed lazily and stored locally.
// Its main use case is for uses-case where faster increments are needed.
counter3.add(5).thenAccept(aVoid -> System.out.println("Adding 5 to counter-3 completed!")).get();
// Check the counter value.
System.out.println("Counter-3 value is " + counter3.getValue());
Step 4: Run the Tutorial
mvn package exec:java
You should see output showing counter values, bound exceptions, listener events, and compare-and-set results.
What’s Next
-
Get started with basic cache operations
-
Use multimap caches for multiple values per key


