JUC并发编程——CAS

简介: JUC并发编程——CAS

正文


一、什么是CAS


       由于JVM的synchronized重量级锁涉及操作系统内核态下互斥锁的使用,因此其线程阻塞和唤醒都涉及进程在用户态和内核态频繁的切换,导致重量级锁开销大,性能低。CAS,Compare And Swap比较并替换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(E)新值(N)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。CAS也称为自旋锁,在一个(死)循环[for(;;)]里不断进行CAS操作,直到成功为止(自旋操作),实际上,CAS也是一种乐观锁。


Unsafe 提供的CAS方法


public final native boolean compareAndSetReference(Object o, long offset,
                                                       Object expected,
                                                       Object x);
public final native boolean compareAndSetLong(Object o, long offset,
                                                  long expected,
                                                  long x);
public final native boolean compareAndSetLong(Object o, long offset,
                                                  long expected,
                                                  long x);


参数说明


o- 需要操作字段所在的对象


offset-需要操作字段的偏移量 相对对象头,在64位jvm虚拟机偏移量是12,因为markWord占64位,Class pointer(类对象指针)占32位,所以偏移量是12.


expected-期望值,也就是旧值


x-更新的值,也就是新值


在执行Unsafe的CAS方法时,这些方法值首先将内存位置的值与旧值比较,如果匹配那么CPU会自动将内存位置的值更新为新值,并返回true,如果不匹配,CPU不做任何操作,并返回false。当并发修改的线程少,冲突出现的机会少时,自旋次数也会减少,CAS的性能就会很高,反之冲突越多,自旋的次数越多,CAS的性能就会越低。


二、JUC原子类


以AtomicInteger为例


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方式设置整数值


public class AtomicIntegerDemo extends Thread {
    private AtomicInteger atomicInteger;
    AtomicIntegerDemo(AtomicInteger atomicInteger) {
        this.atomicInteger = atomicInteger;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100000; i++) {
            atomicInteger.getAndIncrement();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        AtomicIntegerDemo demo1 = new AtomicIntegerDemo(atomicInteger);
        AtomicIntegerDemo demo2 = new AtomicIntegerDemo(atomicInteger);
        AtomicIntegerDemo demo3 = new AtomicIntegerDemo(atomicInteger);
        demo1.start();
        demo2.start();
        demo3.start();
        demo1.join();
        demo2.join();
        demo3.join();
        System.out.println(atomicInteger.get());//300000
    }
}


由上面执行结果可知AtomicInteger是一个原子的操作,并发操作是线程安全的。AtomicInteger主要通过CAS自旋+volatile的方案实现,既保障了变量操作的线程安全性,又避免了synchronized重量级锁的开销。


三、ABA问题


       使用CAS操作内存数据时,数据发生过变化也能更新成功,如A——>B——>A,最后一个CAS预期数据A实际已经发生过更改,但也能修改成功,这就产生了ABA的问题。ABA的解决思路一般是使用版本号,每次变更都带上版本号。JDK提供了两个类AtomicStampedReference和AtomicMarkableReference解决ABA的问题。


       AtomicStampedReference在CAS的基础上增加了一个Stamp(印戳或者标记),使用这个标识可以判断数据是否发生变化。


 public static void main(String[] args) {
        String str1 = "aaa";
        String str2 = "bbb";
        //初始化AtomicStampedReference 初始值是aaa,印戳是1
        AtomicStampedReference<String> reference = new AtomicStampedReference<String>(str1, 1);
        //cas比较str1=aaa ,reference.getStamp()=1,就把str2的值更新到新值,印戳加1
        reference.compareAndSet(str1, str2, reference.getStamp(), reference.getStamp() + 1);
        //当前的值为:bbb印戳是:2
        System.out.println("当前的值为:" + reference.getReference() + "印戳是:" + reference.getStamp());
        boolean flag = reference.weakCompareAndSet(str2, "ccc", 2, reference.getStamp() + 1);
        //更新后的值为:bbb是否更新成功:false,更新失败,因为印戳4和2比较不对
        System.out.println("更新后的值为:" + reference.getReference() + "是否更新成功:" + flag);
    }


       AtomicMarkableReference是AtomicStampedReference的简化版,不关心修改过几次,只关心是否修改过,标志属性是boolean类型,其值只记录是够修改过。


public static void main(String[] args) {
        AtomicMarkableReference<Integer> atomicMarkableReference = new AtomicMarkableReference<Integer>(1, true);
        boolean b = atomicMarkableReference.compareAndSet(1, 2, true, false);
        //是否更新成功true更新后的值:2更新后的标志 mark:false
        System.out.println("是否更新成功" + b + "更新后的值:" + atomicMarkableReference.getReference() + "更新后的标志 mark:" + atomicMarkableReference.isMarked());
        boolean b1 = atomicMarkableReference.compareAndSet(2, 3, true, false);
        //是否更新成功false更新后的值:2更新后的标志 mark:false
        System.out.println("是否更新成功" + b1 + "更新后的值:" + atomicMarkableReference.getReference() + "更新后的标志 mark:" + atomicMarkableReference.isMarked());
    }


总结:


1、操作系统层面的CAS是一条CPU原子指令(cmpxchg指令),由于该指令具有原子性,因此使用CAS不会造成数据不一致的问题。


2、使用CAS无锁编程步骤如下,获取字段中的期望值(旧值)与内存地址上的值作比较(没有修改之前的旧值),如果两个值相等,就把新值放在字段的内存地址上,失败则自旋。


3、并发线程越少,自旋越少CAS性能越高,反之并发线程越多,自旋的次数越多,CAS的性能就越低。


4、jdk中的原子类大都是使用CAS实现的。


5、解决ABA问题使用版本号,jdk中有两个类AtomicStampedReference和AtomicMarkableReference解决ABA的问题。


参考:


《JAVA高并发核心编程(卷2):多线程、锁、JMM、JUC、高并发设计》-尼恩编著

相关文章
|
7月前
|
安全 Java 编译器
高并发编程之什么是 JUC
高并发编程之什么是 JUC
63 1
|
3月前
|
安全 Java API
JAVA并发编程JUC包之CAS原理
在JDK 1.5之后,Java API引入了`java.util.concurrent`包(简称JUC包),提供了多种并发工具类,如原子类`AtomicXX`、线程池`Executors`、信号量`Semaphore`、阻塞队列等。这些工具类简化了并发编程的复杂度。原子类`Atomic`尤其重要,它提供了线程安全的变量更新方法,支持整型、长整型、布尔型、数组及对象属性的原子修改。结合`volatile`关键字,可以实现多线程环境下共享变量的安全修改。
|
6月前
|
SQL 安全 Java
JUC多线程-线程池-Thredalocal-CAS-AQS-死锁
JUC多线程-线程池-Thredalocal-CAS-AQS-死锁
|
6月前
|
安全 算法 Java
|
7月前
|
安全 Java
JUC并发编程之原子类
并发编程是现代计算机应用中不可或缺的一部分,而在并发编程中,处理共享资源的并发访问是一个重要的问题。为了避免多线程访问共享资源时出现竞态条件(Race Condition)等问题,Java提供了一组原子类(Atomic Classes)来支持线程安全的操作。
|
7月前
|
安全 Java
并发编程之CAS、AQS
CAS(Compare and Swap)是一种并发编程中常用的原子操作,用于实现多线程环境下的同步。CAS 操作包括读取一个内存位置的值,与一个期望的值进行比较,如果相等,则更新该内存位置的值。整个操作是原子的,即在执行过程中不会被其他线程中断。
62 0
|
7月前
|
缓存 算法 Java
JUC并发编程之CAS
CAS,即Compare and Swap,是一种并发编程中用于实现多线程环境下的原子操作的技术。它是一种无锁算法,用于解决多线程环境下的数据同步问题。CAS操作包含三个操作数:内存位置V,旧的预期值A和即将要写入的新值B。只有当内存位置的值与旧的预期值A相等时,才会将新值B写入内存位置V,否则不执行任何操作。CAS操作是原子的,保证了多线程环境下的数据一致性和线程安全性。
|
7月前
|
缓存 安全 Java
JUC并发编程之volatile详解
Java内存模型是Java虚拟机(JVM)规范中定义的一组规则,用于屏蔽各种硬件和操作系统的内存访问差异,保证多线程情况下程序的正确执行。Java内存模型规定了线程之间如何交互以及线程和内存之间的关系。它主要解决的问题是可见性、原子性和有序性。
108 0
|
安全 Java 调度
JUC并发编程(上)
JUC并发编程(上)
77 0
|
并行计算 Java 应用服务中间件
JUC并发编程超详细详解篇(一)
JUC并发编程超详细详解篇
1680 1
JUC并发编程超详细详解篇(一)