Implemented the following methods for
`AtomicDouble` and `AtomicDoubleArray`:
* `accumulateAndGet`
* `getAndAccumulate`
* `updateAndGet`
* `getAndUpdate`
Closes https://github.com/google/guava/pull/5784.
Fixes https://github.com/google/guava/issues/5742.
RELNOTES=Implement accumulate/update methods for `AtomicDouble` and `AtomicDoubleArray`.
PiperOrigin-RevId: 412110543
diff --git a/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java b/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java
index 038e2ec..22a793b 100644
--- a/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java
+++ b/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java
@@ -13,6 +13,10 @@
package com.google.common.util.concurrent;
+import static java.lang.Math.max;
+
+import com.google.common.annotations.GwtIncompatible;
+import com.google.common.testing.NullPointerTester;
import java.util.Arrays;
/** Unit test for {@link AtomicDoubleArray}. */
@@ -48,6 +52,13 @@
assertEquals(Double.doubleToRawLongBits(x), Double.doubleToRawLongBits(y));
}
+ @GwtIncompatible // NullPointerTester
+ public void testNulls() {
+ new NullPointerTester().testAllPublicStaticMethods(AtomicDoubleArray.class);
+ new NullPointerTester().testAllPublicConstructors(AtomicDoubleArray.class);
+ new NullPointerTester().testAllPublicInstanceMethods(new AtomicDoubleArray(1));
+ }
+
/** constructor creates array of given size with all elements zero */
public void testConstructor() {
AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
@@ -211,7 +222,8 @@
assertBitEquals(prev, aa.get(i));
assertFalse(aa.weakCompareAndSet(i, unused, x));
assertBitEquals(prev, aa.get(i));
- while (!aa.weakCompareAndSet(i, prev, x)) {;
+ while (!aa.weakCompareAndSet(i, prev, x)) {
+ ;
}
assertBitEquals(x, aa.get(i));
prev = x;
@@ -261,6 +273,128 @@
}
}
+ /** getAndAccumulate with sum adds given value to current, and returns previous value */
+ public void testGetAndAccumulateWithSum() {
+ AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+ for (int i : new int[] {0, SIZE - 1}) {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ aa.set(i, x);
+ double z = aa.getAndAccumulate(i, y, Double::sum);
+ assertBitEquals(x, z);
+ assertBitEquals(x + y, aa.get(i));
+ }
+ }
+ }
+ }
+
+ /** getAndAccumulate with max stores max of given value to current, and returns previous value */
+ public void testGetAndAccumulateWithMax() {
+ AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+ for (int i : new int[] {0, SIZE - 1}) {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ aa.set(i, x);
+ double z = aa.getAndAccumulate(i, y, Double::max);
+ double expectedMax = max(x, y);
+ assertBitEquals(x, z);
+ assertBitEquals(expectedMax, aa.get(i));
+ }
+ }
+ }
+ }
+
+ /** accumulateAndGet with sum adds given value to current, and returns current value */
+ public void testAccumulateAndGetWithSum() {
+ AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+ for (int i : new int[] {0, SIZE - 1}) {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ aa.set(i, x);
+ double z = aa.accumulateAndGet(i, y, Double::sum);
+ assertBitEquals(x + y, z);
+ assertBitEquals(x + y, aa.get(i));
+ }
+ }
+ }
+ }
+
+ /** accumulateAndGet with max stores max of given value to current, and returns current value */
+ public void testAccumulateAndGetWithMax() {
+ AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+ for (int i : new int[] {0, SIZE - 1}) {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ aa.set(i, x);
+ double z = aa.accumulateAndGet(i, y, Double::max);
+ double expectedMax = max(x, y);
+ assertBitEquals(expectedMax, z);
+ assertBitEquals(expectedMax, aa.get(i));
+ }
+ }
+ }
+ }
+
+ /** getAndUpdate adds given value to current, and returns previous value */
+ public void testGetAndUpdateWithSum() {
+ AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+ for (int i : new int[] {0, SIZE - 1}) {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ aa.set(i, x);
+ double z = aa.getAndUpdate(i, value -> value + y);
+ assertBitEquals(x, z);
+ assertBitEquals(x + y, aa.get(i));
+ }
+ }
+ }
+ }
+
+ /** getAndUpdate subtracts given value to current, and returns previous value */
+ public void testGetAndUpdateWithSubtract() {
+ AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+ for (int i : new int[] {0, SIZE - 1}) {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ aa.set(i, x);
+ double z = aa.getAndUpdate(i, value -> value - y);
+ assertBitEquals(x, z);
+ assertBitEquals(x - y, aa.get(i));
+ }
+ }
+ }
+ }
+
+ /** updateAndGet adds given value to current, and returns current value */
+ public void testUpdateAndGetWithSum() {
+ AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+ for (int i : new int[] {0, SIZE - 1}) {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ aa.set(i, x);
+ double z = aa.updateAndGet(i, value -> value + y);
+ assertBitEquals(x + y, z);
+ assertBitEquals(x + y, aa.get(i));
+ }
+ }
+ }
+ }
+
+ /** updateAndGet subtracts given value to current, and returns current value */
+ public void testUpdateAndGetWithSubtract() {
+ AtomicDoubleArray aa = new AtomicDoubleArray(SIZE);
+ for (int i : new int[] {0, SIZE - 1}) {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ aa.set(i, x);
+ double z = aa.updateAndGet(i, value -> value - y);
+ assertBitEquals(x - y, z);
+ assertBitEquals(x - y, aa.get(i));
+ }
+ }
+ }
+ }
+
static final long COUNTDOWN = 100000;
class Counter extends CheckedRunnable {
diff --git a/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java b/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java
index df9bd8b..0547461 100644
--- a/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java
+++ b/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java
@@ -13,6 +13,8 @@
package com.google.common.util.concurrent;
+import static java.lang.Math.max;
+
/** Unit test for {@link AtomicDouble}. */
public class AtomicDoubleTest extends JSR166TestCase {
@@ -125,7 +127,8 @@
assertBitEquals(prev, at.get());
assertFalse(at.weakCompareAndSet(unused, x));
assertBitEquals(prev, at.get());
- while (!at.weakCompareAndSet(prev, x)) {;
+ while (!at.weakCompareAndSet(prev, x)) {
+ ;
}
assertBitEquals(x, at.get());
prev = x;
@@ -166,6 +169,108 @@
}
}
+ /** getAndAccumulate with sum adds given value to current, and returns previous value */
+ public void testGetAndAccumulateWithSum() {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ AtomicDouble a = new AtomicDouble(x);
+ double z = a.getAndAccumulate(y, Double::sum);
+ assertBitEquals(x, z);
+ assertBitEquals(x + y, a.get());
+ }
+ }
+ }
+
+ /** getAndAccumulate with max stores max of given value to current, and returns previous value */
+ public void testGetAndAccumulateWithMax() {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ AtomicDouble a = new AtomicDouble(x);
+ double z = a.getAndAccumulate(y, Double::max);
+ double expectedMax = max(x, y);
+ assertBitEquals(x, z);
+ assertBitEquals(expectedMax, a.get());
+ }
+ }
+ }
+
+ /** accumulateAndGet with sum adds given value to current, and returns current value */
+ public void testAccumulateAndGetWithSum() {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ AtomicDouble a = new AtomicDouble(x);
+ double z = a.accumulateAndGet(y, Double::sum);
+ assertBitEquals(x + y, z);
+ assertBitEquals(x + y, a.get());
+ }
+ }
+ }
+
+ /** accumulateAndGet with max stores max of given value to current, and returns current value */
+ public void testAccumulateAndGetWithMax() {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ AtomicDouble a = new AtomicDouble(x);
+ double z = a.accumulateAndGet(y, Double::max);
+ double expectedMax = max(x, y);
+ assertBitEquals(expectedMax, z);
+ assertBitEquals(expectedMax, a.get());
+ }
+ }
+ }
+
+ /** getAndUpdate with sum stores sum of given value to current, and returns previous value */
+ public void testGetAndUpdateWithSum() {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ AtomicDouble a = new AtomicDouble(x);
+ double z = a.getAndUpdate(value -> value + y);
+ assertBitEquals(x, z);
+ assertBitEquals(x + y, a.get());
+ }
+ }
+ }
+
+ /**
+ * getAndUpdate with subtract stores subtraction of value from current, and returns previous value
+ */
+ public void testGetAndUpdateWithSubtract() {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ AtomicDouble a = new AtomicDouble(x);
+ double z = a.getAndUpdate(value -> value - y);
+ assertBitEquals(x, z);
+ assertBitEquals(x - y, a.get());
+ }
+ }
+ }
+
+ /** updateAndGet with sum stores sum of given value to current, and returns current value */
+ public void testUpdateAndGetWithSum() {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ AtomicDouble a = new AtomicDouble(x);
+ double z = a.updateAndGet(value -> value + y);
+ assertBitEquals(x + y, z);
+ assertBitEquals(x + y, a.get());
+ }
+ }
+ }
+
+ /**
+ * updateAndGet with subtract stores subtraction of value from current, and returns current value
+ */
+ public void testUpdateAndGetWithSubtract() {
+ for (double x : VALUES) {
+ for (double y : VALUES) {
+ AtomicDouble a = new AtomicDouble(x);
+ double z = a.updateAndGet(value -> value - y);
+ assertBitEquals(x - y, z);
+ assertBitEquals(x - y, a.get());
+ }
+ }
+ }
+
/** a deserialized serialized atomic holds same value */
public void testSerialization() throws Exception {
AtomicDouble a = new AtomicDouble();
diff --git a/guava/src/com/google/common/util/concurrent/AtomicDouble.java b/guava/src/com/google/common/util/concurrent/AtomicDouble.java
index 81da695..510dfc0 100644
--- a/guava/src/com/google/common/util/concurrent/AtomicDouble.java
+++ b/guava/src/com/google/common/util/concurrent/AtomicDouble.java
@@ -14,6 +14,7 @@
package com.google.common.util.concurrent;
+import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.Double.doubleToRawLongBits;
import static java.lang.Double.longBitsToDouble;
@@ -21,6 +22,8 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.j2objc.annotations.ReflectionSupport;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import java.util.function.DoubleBinaryOperator;
+import java.util.function.DoubleUnaryOperator;
/**
* A {@code double} value that may be updated atomically. See the {@link
@@ -155,15 +158,7 @@
*/
@CanIgnoreReturnValue
public final double getAndAdd(double delta) {
- while (true) {
- long current = value;
- double currentVal = longBitsToDouble(current);
- double nextVal = currentVal + delta;
- long next = doubleToRawLongBits(nextVal);
- if (updater.compareAndSet(this, current, next)) {
- return currentVal;
- }
- }
+ return getAndAccumulate(delta, Double::sum);
}
/**
@@ -174,10 +169,69 @@
*/
@CanIgnoreReturnValue
public final double addAndGet(double delta) {
+ return accumulateAndGet(delta, Double::sum);
+ }
+
+ /**
+ * Atomically updates the current value with the results of applying the given function to the
+ * current and given values.
+ *
+ * @param x the update value
+ * @param accumulatorFunction the accumulator function
+ * @return the previous value
+ */
+ @CanIgnoreReturnValue
+ public final double getAndAccumulate(double x, DoubleBinaryOperator accumulatorFunction) {
+ checkNotNull(accumulatorFunction);
+ return getAndUpdate(oldValue -> accumulatorFunction.applyAsDouble(oldValue, x));
+ }
+
+ /**
+ * Atomically updates the current value with the results of applying the given function to the
+ * current and given values.
+ *
+ * @param x the update value
+ * @param accumulatorFunction the accumulator function
+ * @return the updated value
+ * @since NEXT
+ */
+ @CanIgnoreReturnValue
+ public final double accumulateAndGet(double x, DoubleBinaryOperator accumulatorFunction) {
+ checkNotNull(accumulatorFunction);
+ return updateAndGet(oldValue -> accumulatorFunction.applyAsDouble(oldValue, x));
+ }
+
+ /**
+ * Atomically updates the current value with the results of applying the given function.
+ *
+ * @param updateFunction the update function
+ * @return the previous value
+ */
+ @CanIgnoreReturnValue
+ public final double getAndUpdate(DoubleUnaryOperator updateFunction) {
while (true) {
long current = value;
double currentVal = longBitsToDouble(current);
- double nextVal = currentVal + delta;
+ double nextVal = updateFunction.applyAsDouble(currentVal);
+ long next = doubleToRawLongBits(nextVal);
+ if (updater.compareAndSet(this, current, next)) {
+ return currentVal;
+ }
+ }
+ }
+
+ /**
+ * Atomically updates the current value with the results of applying the given function.
+ *
+ * @param updateFunction the update function
+ * @return the updated value
+ */
+ @CanIgnoreReturnValue
+ public final double updateAndGet(DoubleUnaryOperator updateFunction) {
+ while (true) {
+ long current = value;
+ double currentVal = longBitsToDouble(current);
+ double nextVal = updateFunction.applyAsDouble(currentVal);
long next = doubleToRawLongBits(nextVal);
if (updater.compareAndSet(this, current, next)) {
return nextVal;
diff --git a/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java b/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java
index 3adb84b..15c1681 100644
--- a/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java
+++ b/guava/src/com/google/common/util/concurrent/AtomicDoubleArray.java
@@ -13,6 +13,7 @@
package com.google.common.util.concurrent;
+import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.Double.doubleToRawLongBits;
import static java.lang.Double.longBitsToDouble;
@@ -20,6 +21,8 @@
import com.google.common.primitives.ImmutableLongArray;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.concurrent.atomic.AtomicLongArray;
+import java.util.function.DoubleBinaryOperator;
+import java.util.function.DoubleUnaryOperator;
/**
* A {@code double} array in which elements may be updated atomically. See the {@link
@@ -171,15 +174,7 @@
*/
@CanIgnoreReturnValue
public final double getAndAdd(int i, double delta) {
- while (true) {
- long current = longs.get(i);
- double currentVal = longBitsToDouble(current);
- double nextVal = currentVal + delta;
- long next = doubleToRawLongBits(nextVal);
- if (longs.compareAndSet(i, current, next)) {
- return currentVal;
- }
- }
+ return getAndAccumulate(i, delta, Double::sum);
}
/**
@@ -191,10 +186,74 @@
*/
@CanIgnoreReturnValue
public double addAndGet(int i, double delta) {
+ return accumulateAndGet(i, delta, Double::sum);
+ }
+
+ /**
+ * Atomically updates the element at index {@code i} with the results of applying the given
+ * function to the curernt and given values.
+ *
+ * @param i the index to update
+ * @param x the update value
+ * @param accumulatorFunction the accumulator function
+ * @return the previous value
+ */
+ @CanIgnoreReturnValue
+ public final double getAndAccumulate(int i, double x, DoubleBinaryOperator accumulatorFunction) {
+ checkNotNull(accumulatorFunction);
+ return getAndUpdate(i, oldValue -> accumulatorFunction.applyAsDouble(oldValue, x));
+ }
+
+ /**
+ * Atomically updates the element at index {@code i} with the results of applying the given
+ * function to the curernt and given values.
+ *
+ * @param i the index to update
+ * @param x the update value
+ * @param accumulatorFunction the accumulator function
+ * @return the updated value
+ */
+ @CanIgnoreReturnValue
+ public final double accumulateAndGet(int i, double x, DoubleBinaryOperator accumulatorFunction) {
+ checkNotNull(accumulatorFunction);
+ return updateAndGet(i, oldValue -> accumulatorFunction.applyAsDouble(oldValue, x));
+ }
+
+ /**
+ * Atomically updates the element at index {@code i} with the results of applying the given
+ * function to the curernt value.
+ *
+ * @param i the index to update
+ * @param updaterFunction the update function
+ * @return the previous value
+ */
+ @CanIgnoreReturnValue
+ public final double getAndUpdate(int i, DoubleUnaryOperator updaterFunction) {
while (true) {
long current = longs.get(i);
double currentVal = longBitsToDouble(current);
- double nextVal = currentVal + delta;
+ double nextVal = updaterFunction.applyAsDouble(currentVal);
+ long next = doubleToRawLongBits(nextVal);
+ if (longs.compareAndSet(i, current, next)) {
+ return currentVal;
+ }
+ }
+ }
+
+ /**
+ * Atomically updates the element at index {@code i} with the results of applying the given
+ * function to the curernt value.
+ *
+ * @param i the index to update
+ * @param updaterFunction the update function
+ * @return the updated value
+ */
+ @CanIgnoreReturnValue
+ public final double updateAndGet(int i, DoubleUnaryOperator updaterFunction) {
+ while (true) {
+ long current = longs.get(i);
+ double currentVal = longBitsToDouble(current);
+ double nextVal = updaterFunction.applyAsDouble(currentVal);
long next = doubleToRawLongBits(nextVal);
if (longs.compareAndSet(i, current, next)) {
return nextVal;