一、什么是CAS
CAS,compare and swap的缩写,中文翻译成比较并交换。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。
二、从一个案例引出CAS
案例:
public class Test { 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() { @Override public 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(); // 阻塞当前线程,直到计数器的值为0 System.out.println("理论结果:" + 1000 * MAX_TREAD); System.out.println("static count: " + count); System.out.println("AtomicInteger: " + atomicInteger.intValue()); } }
输出:
- 理论结果:10000
- static count: 9994
- AtomicInteger: 10000
三、Java中的Atomic 原子操作包
JUC 并发包中原子类 , 都存放在 java.util.concurrent.atomic 类路径下:
根据操作的目标数据类型,可以将 JUC 包中的原子类分为 4 类:
- 基本原子类
- 数组原子类
- 原子引用类型
- 字段更新原子类
1. 基本原子类
基本原子类的功能,是通过原子方式更新 Java 基础类型变量的值。基本原子类主要包括了以下三个:
- AtomicInteger:整型原子类。
- AtomicLong:长整型原子类。
- AtomicBoolean :布尔型原子类。
2. 数组原子类
数组原子类的功能,是通过原子方式更数组里的某个元素的值。数组原子类主要包括了以下三个:
- AtomicIntegerArray:整型数组原子类。
- AtomicLongArray:长整型数组原子类。
- AtomicReferenceArray :引用类型数组原子类。
3. 引用原子类
引用原子类主要包括了以下三个:
- AtomicReference:引用类型原子类。
- AtomicMarkableReference :带有更新标记位的原子引用类型。
- AtomicStampedReference :带有更新版本号的原子引用类型。
AtomicStampedReference通过引入“版本”的概念,来解决ABA的问题。
4. 字段更新原子类
字段更新原子类主要包括了以下三个:
- AtomicIntegerFieldUpdater:原子更新整型字段的更新器。
- AtomicLongFieldUpdater:原子更新长整型字段的更新器。
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
四、类 AtomicInteger
1、常用的方法:
方法 | 介绍 |
public final int get() | 获取当前的值 |
public final int getAndSet(int newValue) | 获取当前的值,然后设置新的值 |
public final int getAndIncrement() | 获取当前的值,然后自增 |
public final int getAndDecrement() | 获取当前的值,然后自减 |
public final int getAndAdd(int delta) | 获取当前的值,并加上预期的值 |
boolean compareAndSet(int expect, int update) | 通过 CAS 方式设置整数值 |
AtomicInteger 案例:
private static void out(int oldValue,int newValue){ System.out.println("旧值:"+oldValue+",新值:"+newValue); } public static void main(String[] args) { int value = 0; AtomicInteger atomicInteger= new AtomicInteger(0); //取值,然后设置一个新值 value = atomicInteger.getAndSet(3); //旧值:0,新值:3 out(value,atomicInteger.get()); //取值,然后自增 value = atomicInteger.getAndIncrement(); //旧值:3,新值:4 out(value,atomicInteger.get()); //取值,然后增加 5 value = atomicInteger.getAndAdd(5); //旧值:4,新值:9 out(value,atomicInteger.get()); //CAS 交换 boolean flag = atomicInteger.compareAndSet(9, 100); //旧值:4,新值:100 out(value,atomicInteger.get()); }
AtomicInteger 源码解析:
public class AtomicInteger extends Number implements java.io.Serializable { // 设置使用Unsafe.compareAndSwapInt进行更新 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } ...省略 private volatile int value; //自动设置为给定值并返回旧值。 public final int getAndSet(int newValue) { return unsafe.getAndSetInt(this, valueOffset, newValue); } //以原子方式将当前值加1并返回旧值。 public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } //以原子方式将当前值减1并返回旧值。 public final int getAndDecrement() { return unsafe.getAndAddInt(this, valueOffset, -1); } //原子地将给定值添加到当前值并返回旧值。 public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); } ...省略 }
通过源码我们发现AtomicInteger的增减操作都调用了Unsafe 实例的方法,下面我们对Unsafe类做介绍:
五、Unsafe类
Unsafe 是位于 sun.misc 包下的一个类,Unsafe 提供了CAS 方法,直接通过native 方式(封装 C++代码)调用了底层的 CPU 指令 cmpxchg。
Unsafe类,翻译为中文:危险的,Unsafe全限定名是 sun.misc.Unsafe
,从名字中我们可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。
1、Unsafe 提供的 CAS 方法
主要如下: 定义在 Unsafe 类中的三个 “比较并交换”原子方法
/* @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 方法包含四个入参: 包含要修改的字段对象、字段内存位置、预期原值及新值。在执行 Unsafe 的 CAS 方法的时候,这些方法首先将内存位置的值与预期值(旧的值)比较,如果相匹配,那么处理器会自动将该内存位置的值更新为新值,并返回 true ;如果不相匹配,处理器不做任何操作,并返回 false 。
2、获取属性偏移量
Unsafe 提供的获取字段(属性)偏移量的相关操作,主要如下:
/** * @param o 需要操作属性的反射 * @return 属性的偏移量 */ public native long staticFieldOffset(Field field); public native long objectFieldOffset(Field field);
staticFieldOffset 方法用于获取静态属性 Field 在 Class 对象中的偏移量,在 CAS 操作静态属性时,会用到这个偏移量。
objectFieldOffset 方法用于获取非静态 Field (非静态属性)在 Object 实例中的偏移量,在 CAS 操作对象的非静态属性时,会用到这个偏移量。
3、根据属性的偏移量获取属性的最新值:
/** * @param o 字段所属于的对象实例 * @param fieldOffset 字段的偏移量 * @return 字段的最新值 */ public native int getIntVolatile(Object o, long fieldOffset);
六、CAS的缺点
1. ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
JDK 提供了两个类 AtomicStampedReference、AtomicMarkableReference 来解决 ABA 问题。
2. 只能保证一个共享变量的原子操作。一个比较简单的规避方法为:把多个共享变量合并成一个共享变量来操作。 JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,可以把多个变量放在一个 AtomicReference 实例后再进行 CAS 操作。比如有两个共享变量 i=1、j=2,可以将二者合并成一个对象,然后用 CAS 来操作该合并对象的 AtomicReference 引用。
3. 循环时间长开销大。高并发下N多线程同时去操作一个变量,会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低了并发性。
解决 CAS 恶性空自旋的较为常见的方案为:
- 分散操作热点,使用 LongAdder 替代基础原子类 AtomicLong。
- 使用队列削峰,将发生 CAS 争用的线程加入一个队列中排队,降低 CAS 争用的激烈程度。JUC 中非常重要的基础类 AQS(抽象队列同步器)就是这么做的。
- (261条消息) Java中CAS详解_java cas_LiWang__的博客-CSDN博客