What are ABA problems
Anyone with a bit of familiarity with Java knows that CAS is compareAndSwap. The Unsafe class in Java provides native methods for manipulating memory directly, including an implementation of compareAndSwap.
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);Copy the code
Var1 here refers to the current object, var2 is the offset of a property of var1 in the current object, var4 is the expected value of the property of the offset var2, var5 is the value to be swapped, if the property of the object is the expected value, the exchange succeeds, otherwise the exchange fails, this is an atomic operation, Only one thread will succeed. So it’s also the premise of many Java thread-safe implementations.
For example, AtomicInteger Count starts with a value of 5 and two threads expect it to be set to 10; Both threads now call compareAndSet (also compareAndSwapObject)
public static void main(String[] args) { final AtomicInteger count = new AtomicInteger(5); for (int i = 0; i < 2; i++) { Thread thread = new Thread(() -> { try { Thread.sleep(10); } catch (Exception ignore) { } boolean re = count.compareAndSet(5, 10); System.out.println(Thread.currentThread().getName() + " compareAndSet " + re); }); thread.start(); }}Copy the code
As we hoped, only one thread would succeed.
Now if you have another thread set 10 to 5
public static void main(String[] args) { final AtomicInteger count = new AtomicInteger(5); for (int i = 0; i < 2; i++) { Thread thread = new Thread(() -> { try { Thread.sleep(10); } catch (Exception ignore) { } boolean re = count.compareAndSet(5, 10); System.out.println(Thread.currentThread().getName() + " compareAndSet " + re); }); thread.start(); } Thread thread = new Thread(() -> { try { Thread.sleep(10); } catch (Exception ignore) { } boolean re = count.compareAndSet(10, 5); System.out.println(Thread.currentThread().getName() + " compareAndSet "+ re); }); thread.start(); }Copy the code
You might see something like this:
Normal? Normal and not normal. Normal as we understand it might look something like this, but do it a few times and you’ll see that it might look something like this:
We expect only a thread of 5 set to 10 successful results for two, like a cost to the user, the balance of 5 pieces, prepaid phone to 10 blocks, the load point for two times, at the same time also use this account to pay a sum of $5 orders, the desired cost is only a successful, payment can be successful, a result according to the equivalent of prepaid phone 2 times 5 dollars. This is the ABA problem in CAS.
How does AtomicStampedReference solve ABA problems
How to solve it? Each time after compareAndSwap, add 1 to the version number of the data. The next compareAndSwap compares both the data and the version number. If the value is the same, the execution fails even if the version number is different. An AtomicStampedReference is provided in Java to solve this problem
public static void main(String[] args) { final AtomicStampedReference<Integer> count = new AtomicStampedReference<>(5, 1); for (int i = 0; i < 2; i++) { Thread thread = new Thread(() -> { try { Thread.sleep(10); } catch (Exception ignore) { } boolean re = count.compareAndSet(5, 10, 1, 2); System.out.println(Thread.currentThread().getName() + "[recharge] compareAndSet " + re); }); thread.start(); } Thread thread = new Thread(() -> { try { Thread.sleep(10); } catch (Exception ignore) { } boolean re = count.compareAndSet(10, 5, count.getStamp(), count.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "[consume] compareAndSet "+ re); }); thread.start(); }Copy the code
This ensures that the recharge will only be successful once. So how does it get the version number updated every time?
AtomicStampedReference internally maintains a Pair data structure, which is volatile to ensure visibility and is used to package data objects and version numbers
private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; }}Copy the code
Its compareAndSet method is as follows
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; returnexpectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }Copy the code
-
First determine whether the parameters passed in match
Pair
The expected, from the data and version number of two aspects to judge, there is a do not meet the call back; -
If the parameter is passed with
Pair
As in, return directly
true
, do not update; -
use
casPair
To compare and swap the current
Pair
With the passed parameter
Pair
; -
casPair
Call again
compareAndSwapObject
To interact
Pair
Properties.
private boolean casPair(Pair<V> cmp, Pair<V> val) { returnUNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); }Copy the code
To put it simply, atom Stampedreference adds the version number to solve the ABA problem of CAS. As for how to add a version number, since compareAndSwapObject can only compare and interact with one object, all you need to do is pack the data and version number into one object.
Java provides an AtomicMarkableReference, which is similar to an AtomicStampedReference except that your version number is replaced with a bool, You only care about whether the data is “modified”, whereas AtomicStampedReference cares about how many times the data is modified.