定义与原理
COMPARE AND SWAP ,比较并替换;
比较如果为预期值,则按设定值进行替换
实例演示
实例1、SWAP实例演示
private static void test01() {
//初始化默认值为0
AtomicInteger inte=new AtomicInteger();
//变1
inte.getAndIncrement();
//如果为1,则变成30
System.out.println(inte.compareAndSet(1, 30));
//如果为30,则变成5
System.out.println(inte.compareAndSet(30, 5));
}
结果:
true
true
实例2、getAndIncrement() 方法源码解析:
// unsafe可以直接操作内存
public final int getAndIncrement() {
// this 调用的对象
// valueOffset 当前这个对象的值的内存地址偏移值
// 1
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5; // ?
do { // 自旋锁(就是一直判断!)
// var5 = 获得当前对象的内存地址中的值!
var5 = this.getIntVolatile(this, valueOffset); // 1000万
// compareAndSwapInt 比较并交换
// 比较当前的值 var1 对象的var2地址中的值是不是 var5,如果是则更新为 var5 + 1
// 如果是期望的值,就交换,否则就不交换!
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
缺点:
1、循环开销很大!
2、内存操作,每次只能保证一个共享变量的原子性!
3、出现ABA 问题?
实例3、ABA问题:
多个线程针对一个资源同时修改,此资源值被线程修改多次后变回原来的初始值,此刻cas无法识别其值是否已发送过变动,直接进行识别替换。
例如 值 t=5 ,线程A修改后为4,线程B修改后为5,此时线程C判断如果t=5,则值变为6;
static AtomicInteger common=new AtomicInteger(5);
//ABA问题演示
private static void test02() throws InterruptedException {
CountDownLatch count=new CountDownLatch(2);
new Thread(()->{
common.getAndIncrement();
System.out.println(common.get());
count.countDown();
}).start();
new Thread(()->{
common.getAndDecrement();
System.out.println(common.get());
count.countDown();
}).start();
count.await();
System.out.println("如果为5则替换为3:"+common.compareAndSet(5, 3));
}
结果:
6
5
如果为5则替换为3:true
实例4、ABA问题解决, 采用AtomicStampedReference方式,比较版本号和值
static AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference<>(100,1);
private static void test03() {
// 其他人员 小花,需要每次执行完毕 + 1
new Thread(()->{
int stamp = atomicReference.getStamp();// 获得版本号
System.out.println("T1 stamp01=>"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicReference.compareAndSet(100,101,
atomicReference.getStamp(),atomicReference.getStamp()+1);
System.out.println("T1 stamp02=>"+atomicReference.getStamp());
atomicReference.compareAndSet(101,100,
atomicReference.getStamp(),atomicReference.getStamp()+1);
System.out.println("T1 stamp03=>"+atomicReference.getStamp());
},"T1").start();
// 乐观的小明
new Thread(()->{
int stamp = atomicReference.getStamp();// 获得版本号
System.out.println("T2 stamp01=>"+stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicReference.compareAndSet(100, 1, stamp, stamp + 1);
System.out.println("T2 是否修改成功:"+ result);
System.out.println("T2 stamp02=>"+atomicReference.getStamp());
System.out.println("T2 当前获取得最新的值=>"+atomicReference.getReference());
},"T2").start();
}
结果:
T2 stamp01=>1
T1 stamp01=>1
T1 stamp02=>2
T1 stamp03=>3
T2 是否修改成功:false
T2 stamp02=>3
T2 当前获取得最新的值=>100
应用场景
CAS是一种应用理念,即乐观锁机制;采用CAS方式也可进行数据库更新
举例说明:
库存表,初始化值 5,版本号 1;其下分别有三个线程 A、B、C,
线程A :将库存更新为 4,版本号更新为 2
线程B :将库存更新为 5,版本号更新为 3
线程C: 如果值为5,且版本号为1,则更新
执行顺序为: A->B->C .
结果推导: 版本号为3,C不会更新