概述
- CAS:CompareAndSwap 比较并交换
定义:CAS包含三个参数,分别是内存位置(V)、期望值(A)、更新值(B),也即是说内存位置的值和期望值是一致的,就是将值更新为更新值。 ```java public class RequestDemo { static int count = 0;
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(100);for (int i = 0; i < 100; i++) {new Thread(() -> {try {for (int i1 = 0; i1 < 10; i1++) {try {request();} catch (InterruptedException e) {e.printStackTrace();}}} finally {latch.countDown();}}, String.valueOf(i)).start();}latch.await();System.out.println(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;
}
public static void request() throws InterruptedException {
Thread.sleep(5);int expectCount;while (!compareAndSwap(expectCount = getCount(), expectCount + 1)) {}
} }
<a name="rF84i"></a># JDK提供的CAS```javapublic final class Unsafe {// 参数1 表示要操作的对象// 参数2 表示要操作对象中属性地址的偏移量// 参数3 预期值// 参数4 需要更新的值public final native boolean compareAndSwapObject(Object o, long offset,Object expected,Object x);public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);}
CAS的实现原理
- CAS通过调用JNI实现,JNI:Java Native Interface 允许Java调用其他语言,而CompareAndSwapXxx系列的方法就是借助”C语言”来调用CPU底层指令实现的,以Intel x86来说,最终映射到CPU的指令就是”cmpxchg”,这是一个原子指令,实现并比较替换的操作。
cmpxchg 如何保证多核心下的线程安全:系统底层进行CAS操作的时候,会判断当前操作系统是否是多核心,如果是,就给”总线”加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行CAS操作,也就说CAS是平台级别的。
CAS的问题和解决
CAS会存在ABA问题,也就是CAS在操作的时候会检查当前的值和期望的值是否是一样的,如果没有变化则更新,但是如果一个值原来是A,在CAS方法执行之前,被其他线程修改为了B,然后又修改成了A,那么这个时候看起来没有发生变化,CAS也是可以执行成功的,但是实际上这个值已经做了改变。
- 如何解决ABA问题,为每个值增加一个唯一的版本号。
JDK提供了解决的方式
AtomicStampedReference和AtomicMarkableReferencepublic class AtomicStampedReference<V> {private static class Pair<T> {// 引用final T reference;// 版本戳final int stamp;private Pair(T reference, int stamp) {this.reference = reference;this.stamp = stamp;}static <T> Pair<T> of(T reference, int stamp) {return new Pair<T>(reference, stamp);}}// expectedReference 期望的引用// newReference 新的引用// expectedStamp 期望的版本号// newStamp 新的版本号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)));}// cas Pair// CAS 更新private boolean casPair(Pair<V> cmp, Pair<V> val) {return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);}}
```java public class AbaDemo1 { private static AtomicInteger atomicInteger = new AtomicInteger(1);
public static void main(String[] args) {
new Thread(() -> {int expNum = atomicInteger.get();int newNum = expNum + 1;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}boolean res = atomicInteger.compareAndSet(expNum, newNum);System.out.println(Thread.currentThread().getName() + " " + res);}, "t1").start();new Thread(() -> {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}int incre = atomicInteger.incrementAndGet();int decre = atomicInteger.decrementAndGet();System.out.println(Thread.currentThread().getName() + " incre: " + incre + ",decre: " + decre);}, "t1").start();
} }
```java/*** 版本号没有问题,但是人为的把版本号瞎搞,就有问题** @author icanci* @since 1.0 Created in 2022/06/05 22:45*/public class AbaDemo2 {private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1, 1);public static void main(String[] args) {new Thread(() -> {int oldReference = atomicStampedReference.getReference();int newReference = oldReference + 1;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}int stamp = atomicStampedReference.getStamp();int newStamp = stamp + 1;boolean res = atomicStampedReference.compareAndSet(oldReference, newReference, stamp, newStamp);System.out.println(Thread.currentThread().getName() + " " + res);}, "t1").start();new Thread(() -> {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}Integer reference = atomicStampedReference.getReference();int stamp = atomicStampedReference.getStamp();boolean b = atomicStampedReference.compareAndSet(reference, reference + 1, stamp, stamp + 1);boolean b1 = atomicStampedReference.compareAndSet(reference + 1, reference, stamp + 1, stamp);System.out.println(Thread.currentThread().getName() + " incre: " + b + ",decre: " + b1);}, "t1").start();}}
public class AbaDemo3 {private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1, 1);public static void main(String[] args) {new Thread(() -> {int oldReference = atomicStampedReference.getReference();int newReference = oldReference + 1;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}int stamp = atomicStampedReference.getStamp();int newStamp = stamp + 1;boolean res = atomicStampedReference.compareAndSet(oldReference, newReference, stamp, newStamp);System.out.println(Thread.currentThread().getName() + " " + res);}, "t1").start();new Thread(() -> {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}Integer reference = atomicStampedReference.getReference();int stamp = atomicStampedReference.getStamp();boolean b = atomicStampedReference.compareAndSet(reference, reference + 1, stamp, stamp + 1);boolean b1 = atomicStampedReference.compareAndSet(reference + 1, reference, stamp + 1, stamp + 1 + 1);System.out.println(Thread.currentThread().getName() + " incre: " + b + ",decre: " + b1);}, "t1").start();}}
