java里的CAS
什么是cas?
cas全称compare-And-Swap,简单理解就是比较更新,和内存中指定位置上的值进行比较,如果期望的值和自己的值一致,则把指定位置上的值替换为自己想要的值。他是sun.misc.Unsafe类下的native操作,操作的过程是原子操作。(Don‘’t talk, show the code!)
##下面是cas一个简单的应用(AtomicInteger相信大家都并不陌生)
public class LockDemo {
AtomicInteger i = new AtomicInteger(0);
private void add() {
i.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
LockDemo demo = new LockDemo();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
demo.add();
}
}).start();
}
Thread.sleep(2000L);
System.out.printf("i=" + demo.i);
}
}
下面是运行结果:
下面我们一起看看AtomicInteger是怎么实现原子操作的:
- AtomicInteger的原子加法(其实该类的几个方法原理都差不太多);
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
这个里面调用了unsafe的getAndAddInt方法,我们且去看一看这个方法:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//循环获取该对象在valueOffset上面的值
var5 = this.getIntVolatile(var1, var2);
//cas+1操作失败,重试获取最新值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
其中的compareAndSwapInt就是cas操作的体现,下面是jdk源方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
cas应用广泛,比如可重入锁(ReentrantLock),信号量(Semaphore),监视器(CountDownLanch),线程栅栏(CyclicBarrier)都有它的踪迹。
2.下面我们尝试自己去写一个类似的加法,以便于深入理解CAS
public class LockDemo1 {
volatile int value = 0;
static Unsafe unsafe; //直接操作内存 修改对象 ,数组内存 强大的api
private static long valueOffset = 0;
static {
try {
//反射技术获取unsafe值
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
//获取到value 属性偏移量 (用于定位value在内存中的地址)
valueOffset = unsafe.objectFieldOffset(LockDemo1.class.getDeclaredField("value"));
} catch (Exception var) {
var.printStackTrace();
}
}
private void add() {
//i++ //JAVA三个步骤
//CAS +循环 重试
int current;
do {
current = unsafe.getIntVolatile(this, valueOffset);
/**
* public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。
*
* @param obj 需要更新的对象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return 如果field的值被更改返回true
*/
} while (!unsafe.compareAndSwapInt(this, valueOffset, current, current + 1));//可能会失败
}
public static void main(String[] args) throws InterruptedException {
LockDemo1 demo = new LockDemo1();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
demo.add();
}
}).start();
}
Thread.sleep(2000L);
System.out.printf("i =" + demo.value);
}
}
其执行结果和上面的AtomicInteger 一模一样
3.Cas有没有什么缺点呢?
答案是有。比如上面的do while循环中 如果cas操作一直失败,那么循环就会一直处于重试;还有就是有名的ABA问题,线程T把资源的A值进行改变从A改为B再次A,如果另外的线程获取到的最新值还是A,那么问题就会产生,其中有名的解决方法就是修改的时候加入版本号的概念。