理解CAS
什么是CAS
Compare And Swap 比较并交换
JUC 有两大核心:CAS 和 AQS
- CAS是java.concurrent.automic包的基础
- AQS是java.concurrent.locks包的基础
- CAS是一条并发原语
- 判断值是否到达预期值,如果到达了预期值,就更新
- 如果预期值是5,更新值是9,那么如果该数据到达了5,就更新为9
- 过程是原子性的
- 调用sun.msic.Unsafe类中的方法
- 因为java是没有办法操作硬件,但是c++可以操作硬件,于是通过Unsafe这个类中的元素操作C++进而操作硬件
为什么会出现CAS
说明:
- 线程
因为,线程的创建与销毁是非常消耗时间的,而线程的执行时间是非常短的,所以出现了线程池,只需要创建一次线程,下次使用的时候,就可以直接使用线程池中的线程了
- synchronized锁
之前就谈论过,表示synchronized是不太友好的操作,在执行到锁时,会经历用户态与内核态的转变,非常耗时,而synchronized是一个很小的区域,就是细粒度很高的操作,而为了这一点点的操作去耗时,是非常不划算的
因此出现了CAS
通过AtomicInteger介绍CAS
自己的方法调用
java
atomicInteger.getAndIncrement();
AtomicInteger类
java
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
通过unsafe类获取地址偏移量,但是具体是如何获得的,应该是在c++中操作的,在java中是没有写的
Unsafe类
java
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
-
var 5 是通过AtomicInteger类和地址偏移量获得的值
-
循环比较,通过地址(var1 ,var2)得到的值与car是否相等,如果相等就让,var5 = var5 + var4
-
var4 = 1;是传入的数值
-
采用的叫自旋锁
-
因为是操作内存的,所以调度是非常快的
上述最后是调用的compareAndSwapInt()方法
AtomicInteger自己也对上面的方法进行了封装
java
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
我们也可以直接调用这个类
缺点:
1、循环耗时
2、一次性只能保证一个共享变量的原子性
3、会产生ABA问题
CAS:ABA问题
什么是ABA问题
就是线程A,先获得了number的值 = 1,然后准备操作,但是没有操作 ,
线程B ,也拿到了number的值,然后将值 = 3 ,然后再次操作, number = 1,改回去了
但是线程A却什么都不知道
解决CAS问题
使用AtomicStampedReference,含有时间戳的类,也可以叫版本号
每次修改都需要对版本 + 1,这样就可以防治ABA问题
java
public class CasTest {
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1,1);
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("线程a将值改变了");
System.out.println(atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("线程a将值改回来了");
System.out.println(atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
},"a").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(atomicStampedReference.compareAndSet(1, 5
, stamp, stamp + 1));
},"b").start();
}
}
- 线程B先拿到了版本,但是没有操作,延迟了2秒
- 线程B在这期间,时间为1秒的时候拿到了版本,这个时候,每次执行都+1,这样线程a修改了值后,线程b就不再去对已经修改过的值进行操作了