wordpress小说站群,wordpress媒体库删除,专业的广州手机网站,邯郸装修网站建设CAS是什么
CAS是compare and swap的缩写#xff0c;即我们所说的比较交换。该操作的作用就是保证数据一致性、操作原子性。
cas是一种基于锁的操作#xff0c;而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住#xff0c;等之前获得锁的线程释放锁之后即我们所说的比较交换。该操作的作用就是保证数据一致性、操作原子性。
cas是一种基于锁的操作而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住等之前获得锁的线程释放锁之后下一个线程才可以访问。而乐观锁采取了一种宽泛的态度通过某种方式不加锁来处理资源比如通过给记录加version来获取数据性能较悲观锁有很大的提高。 CAS 操作包含三个操作数 —— 内存位置V、预期原值A和新值(B)。如果内存地址里面的值和A的值是一样的那么就将内存里面的值更新成B。否则将不做任何处理。 CAS是通过无限循环来获取数据的若果在第一轮循环中a线程获取地址里面的值被b线程修改了那么a线程需要自旋到下次循环才有可能机会执行。 为什么说CAS能很好的保证数据一致性因为它是直接从硬件层面保证了原子性。
CAS是一条CPU的原子指令(cmpxchg指令Unsafe提供的CAS方法如compareAndSwapXXX底层实现即为CPU指令cmpxchg。
执行cmpxchg指令的时候会判断当前系统是否为多核系统如果是就给总线加锁只有一个线程会对总线加锁成功加锁成功之后会执行cas操作也就是说CAS的原子性实际上是CPU实现独占的。比起用synchronized重量级锁 这里的排他时间要短很多 所以在多线程情况下性能会比较好。
测试案例讲解
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;public class TestCas {public static int count 0;private final static int MAX_TREAD10;public static AtomicInteger atomicInteger new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {/*CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后计数器的值就会减一。当计数器的值为0时表示所有的线程都已经完成一些任务然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。*/CountDownLatch latch new CountDownLatch(MAX_TREAD);//匿名内部类Runnable runnable new Runnable() {Overridepublic void run() {for (int i 0; i 1000; i) {count;atomicInteger.getAndIncrement();}latch.countDown(); // 当前线程调用此方法则计数减一}};//同时启动多个线程for (int i 0; i MAX_TREAD; i) {new Thread(runnable).start();}latch.await(); // 阻塞当前线程直到计数器的值为0System.out.println(理论结果 1000 * MAX_TREAD);System.out.println(static count: count);System.out.println(AtomicInteger: atomicInteger.intValue());}
}我们发现每次运行atomicInteger 的结果值都是正确的count的结果却不对
为什么AtomicInteger类的操作能保证数据一致性呢个进入它的源码
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);
}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;
}
//cas方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);可以看到最终是调用了Unsafe类中的compareAndSwapInt该方法就是CAS方法其中的一个。Unsafe类中总共有三个涉及CAS的方法
/*
param o 包含要修改的字段的对象
param offset 字段在对象内的偏移量
param expected 期望值旧的值
param update 更新值新的值
return true 更新成功 | false 更新失败
*/
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);
public final native boolean compareAndSwapInt( Object o, long offset, int expected,int update);
public final native boolean compareAndSwapLong( Object o, long offset, long expected, long update);在执行 Unsafe 的 CAS 方法的时候这些方法首先将内存位置的值与预期值旧的值比较如果相匹配那么处理器会自动将该内存位置的值更新为新值并返回 true 如果不相匹配处理器不做任何操作并返回 false 。
总的来说AtomicInteger类主要利用CAS (compare and swap) volatile和 native方法来保证原子操作。
通过看上述getAndAddInt方法源码可见如果cas失败会不断循环重复尝试。这就常说的cas自旋所谓自旋其实是重复调用compareAndSwap方法涉及到的方法有以下几个也是在Unsafe类中。
getAndAddIntgetAndAddLonggetAndSetIntgetAndSetLonggetAndSetObject
CAS缺点 ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化如果没有发生变化则更新但是如果一个值原来是A变成了B又变成了A那么使用CAS进行检查时会发现它的值没有发生变化但是实际上却变化了。 循环时间长开销大。高并发下N多线程同时去操作一个变量会造成大量线程CAS失败然后处于自旋状态导致严重浪费CPU资源降低了并发性。
ABA问题解决方案AtomicStampedReference
JDK 的提供了一个类似 AtomicStampedReference 类来解决 ABA 问题。
AtomicStampReference 在 CAS 的基础上增加了一个 Stamp 整型 印戳或标记使用这个印戳可以来觉察数据是否发生变化给数据带上了一种实效性的检验。 AtomicStampReference 的 compareAndSet 方法首先检查当前的对象引用值是否等于预期引用并且当前印戳 Stamp 标志是否等于预期标志如果全部相等则以原子方式将引用值和印戳 Stamp 标志的值更新为给定的更新值。 AtomicStampReference 的构造器 /**
* param initialRef初始引用
* param initialStamp初始戳记
*/
AtomicStampedReference(V initialRef, int initialStamp)AtomicStampReference的核心方法 AtomicStampReference类中有自己的compareAndSet方法进入源码 /**
* expectedReference 引用的旧值
* newReference 引用的新值
* expectedStamp 旧的戳记
* newStamp 新的戳记
*/
public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) {PairV current pair;returnexpectedReference current.reference expectedStamp current.stamp ((newReference current.reference newStamp current.stamp) ||casPair(current, Pair.of(newReference, newStamp))); //unsafe类中的cas
}可以看到旧值expectedReference和旧印戳expectedStamp都会进行比较都满足才会调用Unsafe中的cas方法。