什么是CAS
- 比较并交换
// 两个参数分别是:期望值与更新后的值,方法返回值为布尔类型,如果返回true,则进行交换
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
- Unsafe:rt.jar包下的jvm原生类
public final int getAndIncrement() {
// this代表当前对象 , valueOffset代表内存偏移量,偏移量可以简单理解为地址的意思
return unsafe.getAndAddInt(this, valueOffset, 1); // this代表当前对象 , valueOffset
}
-
CAS底层通过系统原语来执行的,原语的执行不允许被打断,从而保证了并发下的原子性
-
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { //获取当前对象与内存地址 var5 = this.getIntVolatile(var1, var2); //修改成功跳出循环,var1代表当前对象,var2代表偏移量,当再一次传入对象和地址时会上上一次传入的进行比较,如果相等,那么就进行修改,取反表示修改成功跳出循环 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
CAS缺点
-
循环时间长,开销时间大(自旋),cpu使用升高
-
只能保证一个共享变量的原子性操作,只能保证一个对象,而加锁可以保证多段
-
ABA问题:线程时间间隔,导致收尾结果一致,中间发生多次交换
-
原子引用更新:解决ABA问题
-
怎么解决ABA问题呢?
-
就是加入时间戳,也就是版本号
-
带时间戳的原子引用类
-
-
package TestDome;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADome {
// 这里要传一个初始值
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
System.out.println("===========以下是ABA问题的产生==============");
new Thread(()->{
// 期望值是100就修改为101;
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"T1").start();
new Thread(()->{
// 线程睡眠一秒,保证T1线程完成一次ABA操作
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
// 期望值是100就修改为101;
System.out.println(atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get());
},"T2").start();
// 睡眠两秒钟,保证上面的ABA彻底完成
try { TimeUnit.SECONDS.sleep(2); }catch (InterruptedException e){ e.printStackTrace(); }
System.out.println("===========以下是ABA问题的解决==============");
new Thread(()->{
int stampe = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t的第一次版本号: "+stampe);
try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); }
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t的第二次版本号: "+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t的第三次版本号: "+atomicStampedReference.getStamp());
},"T3").start();
new Thread(()->{
int stampe = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t的第一次版本号: "+stampe);
try { TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }
boolean result = atomicStampedReference.compareAndSet(100,2019,stampe,stampe+1);
System.out.println(Thread.currentThread().getName()+"\t修改成功否"+result+"\t当前实际最新版本号:"+ atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t当前实际最新值:"+atomicStampedReference.getReference());
},"T4").start();
}
}