what's the difference between compareAndSet and weakCompareAndSet in AtomicReference?
Asked Answered
C

5

26

The source code is the same thing.

public final boolean compareAndSet(V expect, V update) {
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

public final boolean weakCompareAndSet(V expect, V update) {
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

What's the point?

Chancechancel answered 5/4, 2016 at 13:39 Comment(0)
C
14

The weakCompareAndSet javadoc explains it thus:

Atomically sets the value to the given updated value if the current value == the expected value.

May fail spuriously and does not provide ordering guarantees, so is only rarely an appropriate alternative to compareAndSet.

In short the javadoc is saying that the weak version is (or was) a version that provided "weaker" guarantees.

Now, as you observe, the current implementations for these two methods are identical. This is true from Java 6 through Java 8 (at least), based on the source code on the Grepcode site.

So I surmise that the implementations of these two methods were either:

  • originally different, but made the same as a result of an overhaul of the implementation of Unsafe:

    • for expediency (e.g. to save implementation effort
    • because the supposed performance of the "weak" version, or
    • because the "weak" version was problematic; e.g. it was too hard to use correctly.
  • originally the same, and the difference was specified (but not implemented) because the designers thought there might be performance advantages.

The last explanation is unlikely. If two methods are initially implemented the same, reimplementing them as different would risk breaking preexisting code. That is a bad idea, even for Unsafe.


@assylias / @ Stefan Gobel commented an alternative explanation. Basically, the "identical code" that we see in the source code may actually be rewritten by the JIT compiler to give different machine code for the two methods.

This is certainly plausible. The JIT compiler does have special case code generation for some (non-native) method calls: the so-called "intrinsics".


In Java 9, the weakCompareAndSet method was marked as Deprecated. The explanation in the source code is:

This method has plain memory effects but the method name implies volatile memory effects (see methods such as {@link #compareAndExchange} and {@link #compareAndSet}). To avoid confusion over plain or volatile memory effects it is recommended that the method {@link #weakCompareAndSetPlain} be used instead.

On the flipside, we now see that compareAndSet is now implemented differently to weakCompareAndSet / weakCompareAndSetPlain:

public final boolean compareAndSet(V expectedValue, V newValue) {
    return VALUE.compareAndSet(this, expectedValue, newValue);
}

public final boolean weakCompareAndSet(V expectedValue, V newValue) {
    return VALUE.weakCompareAndSetPlain(this, expectedValue, newValue);
}

where VALUE is declared as a java.lang.invoke.VarHandle. The VarHandle methods used above are native and marked as intrinsic candidates.

Conard answered 5/4, 2016 at 13:56 Comment(4)
or more likely: the methods are re-implemented by the JVM (can't remember how it's called).Fact
@Fact intrinsicsVernacularism
Thanks. The first explanation does make sense.Chancechancel
Actually reading the concurrency interest list it seems that the weak method really does the same thing.Fact
M
31

On x86 the LOCK CMPXCHG instruction is used to implement CAS. It is atomic, provides (near-)maximal ordering guarantees and does not suffer from spurious failures. So on x86 platforms there is nothing to gain from a CAS with fewer guarantees.

But on other platforms such as PowerPC or ARM (without LSE extension) CAS is implemented as a sequence of several instructions which provide LL/SC behavior and memory barriers as separate building blocks. This creates some wiggle room how strong your CAS can be both in terms of ordering and failure guarantees. Conversely it means a full-strength CAS can be more costly instruction sequence than required by some concurrent algorithms.

Many concurrent algorithms involve loops that retry or recompute an operation when a CAS fails and then try again. Since LL/SC can fail spuriously a strong CAS implementation based on it has to loop internally. If the code already contains an outer loop it can avoid the inner loop by replacing the strong CAS with a weak CAS that is allowed to fail spuriously.

So weakCAS exists to allow more efficient code on weakly ordered architectures.

The javadoc is vague about what exactly the weakened ordering means because it currently cannot be expressed in terms of the java memory model. That may be revised in the future when it will be more closely aligned with the C++11 memory model.

The table in the Multiprocessor chapter of the JSR-133 Cookbook provides an overview how platforms differ.

Methodius answered 5/4, 2016 at 14:55 Comment(2)
Excellent answer.Narcose
on weakly ordered architectures. - more specifically, on architectures that don't provide a single-instruction CAS. ARMv8.1 is still weakly-ordered, but adds single-instruction atomic RMWs (with relaxed, acquire, release, and seq_cst semantics available.) godbolt.org/z/T7PcefncE / anandtech.com/show/15578/… compares perf. Historically, most RISCs had weakly-ordered memory models and provided atomic RMWs via separate LL and SC instructions which could spuriously fail, but those are two separate design choices.Hysterectomy
C
14

The weakCompareAndSet javadoc explains it thus:

Atomically sets the value to the given updated value if the current value == the expected value.

May fail spuriously and does not provide ordering guarantees, so is only rarely an appropriate alternative to compareAndSet.

In short the javadoc is saying that the weak version is (or was) a version that provided "weaker" guarantees.

Now, as you observe, the current implementations for these two methods are identical. This is true from Java 6 through Java 8 (at least), based on the source code on the Grepcode site.

So I surmise that the implementations of these two methods were either:

  • originally different, but made the same as a result of an overhaul of the implementation of Unsafe:

    • for expediency (e.g. to save implementation effort
    • because the supposed performance of the "weak" version, or
    • because the "weak" version was problematic; e.g. it was too hard to use correctly.
  • originally the same, and the difference was specified (but not implemented) because the designers thought there might be performance advantages.

The last explanation is unlikely. If two methods are initially implemented the same, reimplementing them as different would risk breaking preexisting code. That is a bad idea, even for Unsafe.


@assylias / @ Stefan Gobel commented an alternative explanation. Basically, the "identical code" that we see in the source code may actually be rewritten by the JIT compiler to give different machine code for the two methods.

This is certainly plausible. The JIT compiler does have special case code generation for some (non-native) method calls: the so-called "intrinsics".


In Java 9, the weakCompareAndSet method was marked as Deprecated. The explanation in the source code is:

This method has plain memory effects but the method name implies volatile memory effects (see methods such as {@link #compareAndExchange} and {@link #compareAndSet}). To avoid confusion over plain or volatile memory effects it is recommended that the method {@link #weakCompareAndSetPlain} be used instead.

On the flipside, we now see that compareAndSet is now implemented differently to weakCompareAndSet / weakCompareAndSetPlain:

public final boolean compareAndSet(V expectedValue, V newValue) {
    return VALUE.compareAndSet(this, expectedValue, newValue);
}

public final boolean weakCompareAndSet(V expectedValue, V newValue) {
    return VALUE.weakCompareAndSetPlain(this, expectedValue, newValue);
}

where VALUE is declared as a java.lang.invoke.VarHandle. The VarHandle methods used above are native and marked as intrinsic candidates.

Conard answered 5/4, 2016 at 13:56 Comment(4)
or more likely: the methods are re-implemented by the JVM (can't remember how it's called).Fact
@Fact intrinsicsVernacularism
Thanks. The first explanation does make sense.Chancechancel
Actually reading the concurrency interest list it seems that the weak method really does the same thing.Fact
G
4

Also from the java docs, looks like other answers missed this:

The atomic classes also support method weakCompareAndSet, which has limited applicability. On some platforms, the weak version may be more efficient than compareAndSet in the normal case, but differs in that any given invocation of the weakCompareAndSet method may return false spuriously (that is, for no apparent reason). A false return means only that the operation may be retried if desired, relying on the guarantee that repeated invocation when the variable holds expectedValue and no other thread is also attempting to set the variable will eventually succeed. (Such spurious failures may for example be due to memory contention effects that are unrelated to whether the expected and current values are equal.) Additionally weakCompareAndSet does not provide ordering guarantees that are usually needed for synchronization control. However, the method may be useful for updating counters and statistics when such updates are unrelated to the other happens-before orderings of a program. When a thread sees an update to an atomic variable caused by a weakCompareAndSet, it does not necessarily see updates to any other variables that occurred before the weakCompareAndSet. This may be acceptable when, for example, updating performance statistics, but rarely otherwise.

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html#weakCompareAndSet

Greenaway answered 5/8, 2018 at 10:59 Comment(2)
I don't think we missed that at all. And I don't think that this quote actually address the question. The question is asking why compareAndSet and weakCompareAndSet are implemented the same ... despite the javadoc (that you quoted) saying that they are different?Conard
I believe this question is crucial in explaining that there is a performance difference between the two. If you can live with rare false positives from weakCompareAndSet to benefit increased performance, then go for it.Asterisk
M
0

The top answer is great in terms of explaining why you'd use one method instead of the other.

As for when/how they'd be different:

In the standard implementation of the methods, they're indeed identical. They'll have identical results.

However: in Java 21, the implementations are different, and the methods they call have the @IntrinsicCandidate annotation. This means that the JVM is allowed to replace the implementation of that method with a platform-specific method.

Throughout the Java concurrency code, @IntrinsicCandidate is basically shorthand for "we have a default implementation, but if your platform has special instructions for this concurrency primitive, they might be used instead."

So more recent versions of Java indeed can have different implementations, and per the docs, their behavior will be different in general.

Mothy answered 28/2 at 21:4 Comment(0)
H
-1

Functionally, both are the same. The main difference is that weakAtomicCompareAndSet can fail spuriously (see oracle documentation) and does not provide ordering guarantee

The use of atomicCompareAndSet is recommended instead of weak version

Harbinger answered 5/4, 2016 at 13:43 Comment(2)
But why? weakCompareAndSet can fail spuriously but compareAndSet wouldn't. They're exactly the same. :(Chancechancel
They are the same in that implementation, are they the same for every implementation?Correlation

© 2022 - 2024 — McMap. All rights reserved.