Java中CAS详解

简介: Java中CAS详解

一、什么是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());
    }
}

输出:

  1. 理论结果:10000
  2. static count: 9994
  3. 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 操作。比如有两个共享变量 i1j=2,可以将二者合并成一个对象,然后用 CAS 来操作该合并对象的 AtomicReference 引用。

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

解决 CAS 恶性空自旋的较为常见的方案为:


目录
相关文章
|
Java 编译器
解密Java多线程中的锁机制:CAS与Synchronized的工作原理及优化策略
解密Java多线程中的锁机制:CAS与Synchronized的工作原理及优化策略
|
3月前
|
安全 Java API
JAVA并发编程JUC包之CAS原理
在JDK 1.5之后,Java API引入了`java.util.concurrent`包(简称JUC包),提供了多种并发工具类,如原子类`AtomicXX`、线程池`Executors`、信号量`Semaphore`、阻塞队列等。这些工具类简化了并发编程的复杂度。原子类`Atomic`尤其重要,它提供了线程安全的变量更新方法,支持整型、长整型、布尔型、数组及对象属性的原子修改。结合`volatile`关键字,可以实现多线程环境下共享变量的安全修改。
|
4月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
76 1
|
7月前
|
算法 安全 Java
Java多线程基础-12:详解CAS算法
CAS(Compare and Swap)算法是一种无锁同步原语,用于在多线程环境中更新内存位置的值。
73 0
|
5月前
|
安全 Oracle Java
(四)深入理解Java并发编程之无锁CAS机制、魔法类Unsafe、原子包Atomic
其实在我们上一篇文章阐述Java并发编程中synchronized关键字原理的时候我们曾多次谈到过CAS这个概念,那么它究竟是什么?
128 1
|
7月前
|
安全 Java 编译器
Java 多线程系列Ⅴ(常见锁策略+CAS+synchronized原理)
Java 多线程系列Ⅴ(常见锁策略+CAS+synchronized原理)
|
7月前
|
算法 Java
Java中CAS算法的集中体现:Atomic原子类库,你了解吗?
【5月更文挑战第15天】Java中CAS算法的集中体现:Atomic原子类库,你了解吗?
57 1
|
6月前
|
安全 Java
并发编程-Java如何实现原子操作(CAS或锁)
并发编程-Java如何实现原子操作(CAS或锁)
39 0
|
7月前
|
存储 安全 Java
【Java EE】CAS原理和实现以及JUC中常见的类的使用
【Java EE】CAS原理和实现以及JUC中常见的类的使用
|
7月前
|
安全 Java 程序员
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
69 0