当前位置:首页 > 编程笔记 > 正文
已解决

java CAS详解(深入源码剖析)

来自网友在路上 173873提问 提问时间:2023-09-20 03:47:34阅读次数: 73

最佳答案 问答题库738位专家为你答疑解惑

CAS是什么

CAS是compare and swap的缩写,即我们所说的比较交换。该操作的作用就是保证数据一致性、操作原子性。

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_TREAD=10;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类中。

  • getAndAddInt
  • getAndAddLong
  • getAndSetInt
  • getAndSetLong
  • getAndSetObject

CAS缺点

  1. ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。

  2. 循环时间长开销大。高并发下N多线程同时去操作一个变量,会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低了并发性。

ABA问题解决方案(AtomicStampedReference)

JDK 的提供了一个类似 AtomicStampedReference 类来解决 ABA 问题。

AtomicStampReference 在 CAS 的基础上增加了一个 Stamp 整型 印戳(或标记),使用这个印戳可以来觉察数据是否发生变化,给数据带上了一种实效性的检验。
AtomicStampReference 的 compareAndSet 方法首先检查当前的对象引用值是否等于预期引用,并且当前印戳( Stamp )标志是否等于预期标志,如果全部相等,则以原子方式将引用值和印戳( Stamp )标志的值更新为给定的更新值。

  1. AtomicStampReference 的构造器

    /**  
    * @param initialRef初始引用  
    * @param initialStamp初始戳记  
    */
    AtomicStampedReference(V initialRef, int initialStamp)
    
  2. AtomicStampReference的核心方法

    AtomicStampReference类中有自己的compareAndSet方法,进入源码:

    /**
    * expectedReference 引用的旧值
    * newReference 引用的新值
    * expectedStamp 旧的戳记
    * newStamp 新的戳记 
    */
    public boolean compareAndSet(V   expectedReference,V   newReference,int expectedStamp,int newStamp) {Pair<V> 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方法。

查看全文

99%的人还看了

猜你感兴趣

版权申明

本文"java CAS详解(深入源码剖析)":http://eshow365.cn/6-9748-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!