浅谈CAS
前言:前文讨论了volatile保证工作内存和主内存之间的数据一致性问题(可见性),但是运算的原子性没有保证,那么使用CAS就可以用来解决这个原子性运算问题。
CAS,即Compare And Swap,是一种典型乐观锁的实现。来自大佬的定义:CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。Java中有自带的原子类数据类型,例如AtomicInteger,调用unsafe包实现
public class CASDemo {
public static AtomicInteger i = new AtomicInteger(0);
private static final int THREADS_COUNT = 20;
private static void inCrease() {
i.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[THREADS_COUNT];
CountDownLatch countDownLatch = new CountDownLatch(THREADS_COUNT);
for (int j = 0; j < THREADS_COUNT; j++) {
threads[j] = new Thread(new Runnable() {
@Override
public void run() {
for (int k = 0; k < 10000; k++) {
inCrease();
}
countDownLatch.countDown();
}
});
threads[j].start();
}
countDownLatch.await();
System.out.println(i);
}
}
此时输出结果为200000,为线程安全。
unsafe的cas操作在实现上也是在汇编层面lock加锁,所以同时具备volatile的性质:可见性和禁止指令重排序。
CAS也有它的问题,一个是循环开销问题,CAS经常和循环一起使用,执行失败就会重试,如果一个变量的并发操作量很大的时候,这时循环执行就有大量失败和重试次数,造成过大的Cpu开销。
二是ABA问题,CAS只是关心Value和ExpectedValue是否相等,相等则继续执行,那么如果执行期间这个变量被其他线程使用过,但是结果不变,这时就CAS就会误认为该值没有变化过。Java引入了“AtomicStampedReference”,通过同时比较Value的值和版本控制解决了这个问题,MySQL的MVCC(多版本并发控制)也是采取了类似的设计思想。
浙公网安备 33010602011771号