This becomes more manageable and easier to understand using the accumulateAndGet
or getAndAccumulate
introduced in Java 8. These allow you to atomically update the value by supplying an accumulator function that sets the value to the result of the function, and also either returns the previous or calculated result depending on what you need. Here is an example of what that class might look like, followed by a simple example I wrote up that uses it:
import java.math.BigInteger;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
public final class AtomicBigInteger {
private final AtomicReference<BigInteger> bigInteger;
public AtomicBigInteger(final BigInteger bigInteger) {
this.bigInteger = new AtomicReference<>(Objects.requireNonNull(bigInteger));
}
// Method references left out for demonstration purposes
public BigInteger incrementAndGet() {
return bigInteger.accumulateAndGet(BigInteger.ONE, (previous, x) -> previous.add(x));
}
public BigInteger getAndIncrement() {
return bigInteger.getAndAccumulate(BigInteger.ONE, (previous, x) -> previous.add(x));
}
public BigInteger get() {
return bigInteger.get();
}
}
An example using it:
import java.math.BigInteger;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class ABIExample {
private static final int AVAILABLE_PROCS = Runtime.getRuntime().availableProcessors();
private static final int INCREMENT_AMOUNT = 2_500_000;
private static final int TASK_AMOUNT = AVAILABLE_PROCS * 2;
private static final BigInteger EXPECTED_VALUE = BigInteger.valueOf(INCREMENT_AMOUNT)
.multiply(BigInteger
.valueOf(TASK_AMOUNT));
public static void main(String[] args)
throws InterruptedException, ExecutionException {
System.out.println("Available processors: " + AVAILABLE_PROCS);
final ExecutorService executorService = Executors
.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
final AtomicBigInteger atomicBigInteger = new AtomicBigInteger(BigInteger.ZERO);
final List<Callable<Void>> incrementTasks = IntStream.rangeClosed(1, TASK_AMOUNT)
.mapToObj(i -> incrementTask(i, atomicBigInteger))
.collect(Collectors.toList());
final List<Future<Void>> futures = executorService.invokeAll(incrementTasks);
for (Future<Void> future : futures) {
future.get();
}
executorService.shutdown();
executorService.awaitTermination(30, TimeUnit.SECONDS);
System.out.println("Final value: " + atomicBigInteger.get());
final boolean areEqual = EXPECTED_VALUE.equals(atomicBigInteger.get());
System.out.println("Does final value equal expected? - " + areEqual);
}
private static Callable<Void> incrementTask(
final int taskNumber,
final AtomicBigInteger atomicBigInteger
) {
return () -> {
for (int increment = 0; increment < INCREMENT_AMOUNT; increment++) {
atomicBigInteger.incrementAndGet();
}
System.out.println("Task #" + taskNumber + " Completed");
return null;
};
}
}
And an output from running the example on my machine:
Available processors: 8
Task #3 Completed
Task #8 Completed
Task #7 Completed
Task #6 Completed
Task #5 Completed
Task #2 Completed
Task #4 Completed
Task #1 Completed
Task #9 Completed
Task #10 Completed
Task #11 Completed
Task #13 Completed
Task #16 Completed
Task #12 Completed
Task #14 Completed
Task #15 Completed
Final value: 80000000
Does final value equal expected? - true
AtomicReference
does it for you without locking. But unless you need integers larger than longs, I would be surprised ifBigInteger
wasn't slowing you down. Try to convince them to go withAtomicLong
. – Sandrocottus