CAS definition:
The full name of CAS is compare and swap. It is a CPU concurrency primitive. Its function is to judge whether the value of a location in memory is the expected value. If so, it will be changed to a new value. This process is atomic.
The CAS operation contains three operands: memory location (V), expected value (A), and new value (B).
If the value of the memory location matches the expected value, the processor automatically updates the location value to the new value. Otherwise, the processor does nothing. In either case, it returns the value of the location before the CAS instruction. (CAS only returns whether the CAS is successful in some special cases without extracting the current value) CAS effectively states that "I think position V should contain value A; if it contains this value, put B in this position; otherwise, do not change the value of this position, just tell me the current value of this position." it is an optimistic lock different from synchronized
Case:
Thread safety with synchronized
public class CASDemo01 { volatile static int count = 0; public synchronized static void request() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(5); count++; } public static void main(String[] args) throws InterruptedException { long startTime = System.currentTimeMillis(); int threadSize = 100; CountDownLatch countDownLatch = new CountDownLatch(threadSize); for (int i = 0; i < threadSize; i++) { Thread thread = new Thread(() -> { try { for (int j = 0; j < 10; j++) { request(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { countDownLatch.countDown(); } }); thread.start(); } countDownLatch.await(); long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ", time consuming:" + (endTime - startTime) + ",count = " + count); } }
Result: main, time consuming: 6332,count = 1000
Simulating CAS to achieve thread safety
public class SimulationCAS { volatile static int count = 0; public static void request() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(5); int expectCount; while (!compareAndSwap(expectCount = getCount(), expectCount + 1)) { } } public static void main(String[] args) throws InterruptedException { long startTime = System.currentTimeMillis(); int threadSize = 100; CountDownLatch countDownLatch = new CountDownLatch(threadSize); for (int i = 0; i < threadSize; i++) { Thread thread = new Thread(() -> { try { for (int j = 0; j < 10; j++) { request(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { countDownLatch.countDown(); } }); thread.start(); } countDownLatch.await(); long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ", time consuming:" + (endTime - startTime) + ",count = " + count); } public static synchronized boolean compareAndSwap(int expectCount, int newCount) { if (getCount() == expectCount) { count = newCount; return true; } return false; } public static int getCount() { return count; } }
Structure: main, time consuming: 205,count = 1000
Q: How to use the CAS support provided by JDK?
A: java provides support for CAS operations. Specifically, in the sun.misc.unsafe class, it is declared as follows:
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
Parameter var1: indicates the object to operate on
Parameter var2: indicates the offset of the attribute address in the object to be manipulated
Parameter var4; Represents the expected value of the data that needs to be modified
Parameter var5: indicates the new value that needs to be modified to
Q: What is the implementation principle of CAS?
A: CAS is implemented by calling the code of JNI: java Native Interface, which allows java to call other languages. The compareAndSwapxxx series method is implemented by calling the underlying instructions of the cpu with the help of "C language". For the commonly used Intel x86 platform, the instruction finally mapped to the cpu is cmpxchg, which is an atomic instruction. When the cpu executes this command, it realizes the operation of comparison and replacement!
Q: Modern computers have hundreds of cores, cmpxchg how to ensure thread safety under multi-core?
A: When performing CAS operations at the bottom of the system, it will judge whether the current system is a multi-core system. If so, lock the "bus". Only one thread will lock the bus successfully. After locking successfully, CAS operations will be executed, that is, the atomicity of CAS is platform level!
Q: Advantages and disadvantages of CAS
A:
advantage:
No locking, good performance
Disadvantages:
1. If the do while loop takes a long time, the overhead is large (if it is unsuccessful, it will loop all the time. In the worst case, the value obtained by a thread is different from the expected value, so it will loop indefinitely)
2. The atomicity of only one shared variable can be guaranteed
When operating on a shared variable, we can ensure the atomic operation by circulating CAS. However, when operating on multiple shared variables, circulating CAS cannot ensure the atomicity of the operation. At this time, we can only use locks to ensure the atomicity
3.ABA ask questions
// ursafe.getAndAddInt public final int getAndAddInt(Object var1, long var2, int var4){ int var5; do { var5 = this.getIntVolatile(var1, var2); }while(!this.compareAndSwapInt(varl, var2, var5,var5 + var4)); return var5; }
Q: How to solve ABA problem?
A: The simplest solution to ABA is to add a modified version number to the value. Each time the value changes, its version number will be modified. This version number will be compared during CAS operation. The ABA solution in java (AtomicStampedReference)AtomicStampedReference mainly contains an object reference and a pair object of the integer "stamp?" that can be automatically updated to solve the ABA problem.
AtomicStampedReference maintains a Pair object, which encapsulates the reference and timestamp stamp, which is a simple layer of encapsulation.
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && //The expected reference is consistent with the current reference expectedStamp == current.stamp && // The expected version is consistent with the current version ((newReference == current.reference && //If the reference or timestamp of the current pair is the same as the new one, it will not go to the next part newStamp == current.stamp) || //It's already set. Don't repeat the setting casPair(current, Pair.of(newReference, newStamp))); } ```
ABA solution demo
public class ABADemo { private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100, 1); public static void main(String[] args) { System.out.println("=========================Here is ABA problem======================================"); new Thread(()->{ atomicReference.compareAndSet(100,101); atomicReference.compareAndSet(101,100); },"t1").start(); new Thread(()->{ //Pause for 1 second to ensure the completion of ABA try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get()); },"t2").start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("=========================Here is the solution ABA Problem solving method======================================"); new Thread(()->{ int stamp = stampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t First version number"+stamp+"\t Value is"+stampedReference.getReference()); //Pause t3 thread for 1 second try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t Second version number"+stampedReference.getStamp()+"\t Value is"+stampedReference.getReference()); stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 3rd version number"+stampedReference.getStamp()+"\t Value is"+stampedReference.getReference()); },"t3").start(); new Thread(()->{ int stamp = stampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t First version number"+stamp+"\t Value is"+stampedReference.getReference()); //Ensure that thread 3 completes ABA once try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1); System.out.println(Thread.currentThread().getName()+"\t Modified successfully"+result+"\t Latest version number"+stampedReference.getStamp()); System.out.println("Latest value\t"+stampedReference.getReference()); },"t4").start(); } }
=========================Here is ABA problem====================================== true 2019 =========================Here is the solution ABA Problem solving method====================================== t4 1st version No. 1 The value is 100 t3 1st version No. 1 The value is 100 t3 2nd Edition No. 2 The value is 101 t3 3rd Edition No. 3 The value is 100 t4 Modified successfully false Latest version No. 3 Latest value 100