Java CAS操作

news/2025/2/1 5:42:11/

通过前面的学习认识到了CPU缓存,Java内存模型,以及线程安全的原子、可见、顺序三大特性。本文则重点认识CAS操作,这是Java并发编程常见的一个操作,AbstractQueuedSynchronizer基于此操作提供了丰富的同步器和各种锁。

目录

    • CAS(CompareAndSwap)操作
    • CAS 的缺点
    • AtomicInteger 使用 volatile + CAS(保证线程安全)
      • AtomicInteger使用例子
      • `incrementAndGet()`
    • unsafe 的一些方法
      • unsafe.objectFieldOffset()
      • compareAndSwapInt
      • getIntVolatile
      • unsafe线程安全操作例子程序
    • 原子引用(AtomicReference)
      • AtomicStampedReference(用版本解决ABA)
        • ABA问题
        • ABA问题的解决(加上版本)

CAS(CompareAndSwap)操作

  • 用于实现多线程同步原子指令,非阻塞算法,是由CPU硬件实现的(x86下cmpxchg指令;arm下LL/SC指令),无需用到内核的同步原语来实现

CAS通过调用JNI(java native interface)的代码来操作底层指令来实现。

  • Unsafe: compareAndSwap

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

  • jdk8u: unsafe.cpp

cmpxchg = compare and exchange

  • jdk8u atomic_linux_x86.inline.hpp

is_MP = Multi Processor

多CPU情况下会加上Lock(Lock是确保原子性的)

结论:cmpxchg = cas修改变量值, lock cmpxchg 指令;硬件层面:lcok指令在执行后面指令的时候会锁定一个北桥芯片(确保只有一个CPU访问内存)

  • 乐观锁(无锁的乐观机制)

在这里插入图片描述

CAS 的缺点

  1. ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A

(取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差异类会导致数据的变化,内存是变化过的,有中间这个过程)

  1. 自旋问题。循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一:它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二:它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率

  2. 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作

  3. 不公平的锁机制:处于阻塞状态的线程,无法立即竞争被释放的锁;而处于自旋状态的线程,很有可能优先获得锁。即CAS无法实现公平锁机制,而Lock体系可以。

AtomicInteger 使用 volatile + CAS(保证线程安全)

AtomicInteger内部维持着一个volatile的全局变量,且各方法是基于CAS操作,确保是线程安全的

java">public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID = 6214790243416807050L;// setup to use Unsafe.compareAndSwapInt for updatesprivate 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;/*** Creates a new AtomicInteger with the given initial value.** @param initialValue the initial value*/public AtomicInteger(int initialValue) {value = initialValue;}/*** Creates a new AtomicInteger with initial value {@code 0}.*/public AtomicInteger() {}

AtomicInteger使用例子

java">package com.mb;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;public class Main {static SimpleDateFormat ft = new SimpleDateFormat ("yyyy.MM.dd HH:mm:ss SSS");public static int num = 0;public static AtomicInteger atomicInteger = new AtomicInteger(0);public static void atomicAdd() {atomicInteger.incrementAndGet();}public static void add() {num++;}public synchronized static void addSync() {num++;}private final static int N = 30;public static void main(String[] args) throws Exception {Thread[] threads = new Thread[N];System.out.println(ft.format(new Date()));for(int i=0;i<N;i++){threads[i] = new Thread(()->{try{TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));int addCnt = 100;for(int j=0;j<addCnt;j++){
//                       addSync();atomicAdd();}}catch (Exception e){e.printStackTrace();}});threads[i].start();}for(int i=0;i<N;i++) {threads[i].join();}System.out.println(ft.format(new Date()));
//        System.out.println("num:" + num);System.out.println("num:" + atomicInteger);}}
/*output
3000 (因为是原子操作)
*/

incrementAndGet()

java">public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 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;
}public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

unsafe 的一些方法

Unsafe提供了一些低层次操作,如直接内存访问、线程调度等(不安全,不推荐)

unsafe.objectFieldOffset()

java">private static Unsafe unsafe = null;
private static long valueOffset;static {try{Class<?> clazz = Unsafe.class;Field f;f = clazz.getDeclaredField("theUnsafe");f.setAccessible(true);unsafe = (Unsafe) f.get(clazz);valueOffset = unsafe.objectFieldOffset(Main.class.getDeclaredField("value"));} catch (IllegalAccessException e) {e.printStackTrace();}catch (SecurityException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();}
}

JVM的实现可以自由选择如何实现Java对象的"布局",也就是在内存里Java对象的各个部分放在哪里,包括对象的实例字段和一些元数据之类。sun.misc.Unsafe里关于对象字段访问的方法把对象布局抽象出来,它提供了objectFieldOffset()方法用于获取某个字段相对Java对象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java对象的某个字段

compareAndSwapInt

java">public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

即:

java">boolean compareAndSwapInt(Object obj,long fieldoffset, int expect, int update);
内存值V、旧的预期值A、要修改的值B
当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false

即 当要修改obj对象的(fieldoffset)Int属性值与expect相同时,则修改(fieldoffset)Int为update,并返回true,否则什么都不做,返回false

getIntVolatile

java">public native int getIntVolatile(Object var1, long var2);

getIntVolatile方法用于在对象指定偏移地址处volatile读取一个int。

unsafe线程安全操作例子程序

java">public class Main {private static Unsafe unsafe = null;private static long valueOffset;static {try{Class<?> clazz = Unsafe.class;Field f;f = clazz.getDeclaredField("theUnsafe");f.setAccessible(true);unsafe = (Unsafe) f.get(clazz);valueOffset = unsafe.objectFieldOffset(Main.class.getDeclaredField("value"));} catch (IllegalAccessException e) {e.printStackTrace();}catch (SecurityException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();}}private volatile int value;public final int get() {return value;}public final void set(int newValue) {value = newValue;}public void addAndIncrease() {int var5;do {var5 = unsafe.getIntVolatile(this, valueOffset);} while(!unsafe.compareAndSwapInt(this, valueOffset, var5, var5 + 1));}public void add() {value ++;}public synchronized void addSync() {value ++;}private final static int N = 30;public static void main(String[] args) throws Exception {Main m = new Main();m.set(2);Thread[] threads = new Thread[N];for(int i=0;i<N;i++){threads[i] = new Thread(()->{try{TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));int addCnt = 100;for(int j=0;j<addCnt;j++){
//                        m.add();
//                        m.addSync();m.addAndIncrease();}}catch (Exception e){e.printStackTrace();}});threads[i].start();}for(int i=0;i<N;i++) {threads[i].join();}System.out.println("value:" + m.get());}}

原子引用(AtomicReference)

对象进行原子操作,提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference(例如比较和交换操作)将不会使得AtomicReference处于不一致的状态。

AtomicReferenceAtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,底层采用的是compareAndSwapInt实现CAS,比较的是数值是否相等,而AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。

java">class User{String userName;int age;public User(String userName, int age) {this.userName = userName;this.age = age;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"userName='" + userName + '\'' +", age=" + age +'}';}
}public class Main {public static void main(String[] args) {User z3 = new User("z3", 12);User l4 = new User("l4", 15);AtomicReference<User> atomicReference = new AtomicReference<>();atomicReference.set(z3);boolean b = atomicReference.compareAndSet(z3, l4);System.out.println(b);System.out.println(atomicReference.get().toString());b = atomicReference.compareAndSet(z3, l4);System.out.println(b);System.out.println(atomicReference.get().toString());}}

AtomicStampedReference(用版本解决ABA)

ABA问题
java">public class Main {static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);public static void main(String[] args) {new Thread(()->{boolean b = atomicReference.compareAndSet(100, 101); // A B ASystem.out.println(Thread.currentThread().getName() + " " + b + " " + atomicReference.get().toString());b = atomicReference.compareAndSet(101, 100);System.out.println(Thread.currentThread().getName() + " " + b + " " + atomicReference.get().toString());}, "t1").start();new Thread(()->{try{TimeUnit.SECONDS.sleep(1);}catch (Exception e){}// t2 修改成功, 另外一个线程从 100 变成 101 又变成了 100, b比较判断是100,所以能修改成功boolean b = atomicReference.compareAndSet(100, 102);System.out.println(Thread.currentThread().getName() + " " + b + " " + atomicReference.get().toString());}, "t2").start();}
}
ABA问题的解决(加上版本)
java">public class Main {static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);public static void main(String[] args) {new Thread(()->{int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + " " + stamp);try{TimeUnit.SECONDS.sleep(1);}catch (Exception e){}boolean b = atomicStampedReference.compareAndSet(100, 101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1); // A B ASystem.out.println(Thread.currentThread().getName() + " " + b + " " + atomicStampedReference.getStamp());b = atomicStampedReference.compareAndSet(101, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);System.out.println(Thread.currentThread().getName() + " " + b + " " + atomicStampedReference.getStamp());}, "t1").start();new Thread(()->{// t2 修改成功int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + " " + stamp);try{TimeUnit.SECONDS.sleep(4);}catch (Exception e){}boolean b = atomicStampedReference.compareAndSet(100, 102, stamp, stamp + 1);System.out.println(Thread.currentThread().getName() + " " + b + " " + atomicStampedReference.getStamp());}, "t2").start();}}
  • output
java">t1 1
t2 1
t1 true 2
t1 true 3
t2 false 3

t1,t2某时候同一版本
然后t1执行 A->B->A, 不过加上了版本
然后t2判断是有版本变更的,所以CAS操作失败


http://www.ppmy.cn/news/1568348.html

相关文章

从新手到高手的蜕变:MySQL 视图进阶全攻略

一、视图是什么 视图是一种虚拟表&#xff0c;它并非像普通表那样实际存储数据&#xff0c;而是基于 SQL 查询语句定义的。视图是从一个或多个基表&#xff08;实际存在的物理表&#xff09;或其他视图中导出的结果集。可以将其视为一个预定义的查询&#xff0c;当执行针对视图…

Python 之 Excel 表格常用操作

示例文件 test.xlsx 将各个表单拆分成单独的 Excel 文件 import os.pathimport openpyxl import pandasdef handle_excel(file_path):dirname os.path.dirname(file_path)basename os.path.basename(file_path).split(".")[0]wb openpyxl.load_workbook(file_pat…

游戏开发领域 - 游戏引擎 UE 与 Unity

游戏引擎 游戏引擎是用于开发电子游戏的软件框架&#xff0c;它提供图形渲染、物理模拟、音频处理、动画系统、脚本编写等功能&#xff0c;帮助开发者高效创建电子游戏 但是&#xff0c;游戏引擎也不仅限于游戏开发&#xff0c;还广泛应用于其他领域&#xff0c;例如&#xff…

kafka-部署安装

一. 简述&#xff1a; Kafka 是一个分布式流处理平台&#xff0c;常用于构建实时数据管道和流应用。 二. 安装部署&#xff1a; 1. 依赖&#xff1a; a). Java&#xff1a;Kafka 需要 Java 8 或更高版本。 b). zookeeper&#xff1a; #tar fxvz zookeeper-3.7.0.tar.gz #…

Python练习(2)

今日题单 吃鱼还是吃肉 PTA | 程序设计类实验辅助教学平台 降价提醒机器人PTA | 程序设计类实验辅助教学平台 幸运彩票 PTA | 程序设计类实验辅助教学平台 猜帽子游戏 PTA | 程序设计类实验辅助教学平台 谁管谁叫爹 PTA | 程序设计类实验辅助教学平台 就不告诉你 PTA | 程…

Java中的注解与反射:深入理解getAnnotation(Class<T> annotationClass)方法

Java的注解&#xff08;Annotation&#xff09;是一种元数据机制&#xff0c;它允许我们在代码中添加额外的信息&#xff0c;这些信息可以在编译时或运行时被读取和处理。结合Java的反射机制&#xff08;Reflection&#xff09;&#xff0c;我们可以在运行时动态地获取类、方法…

Android NDK

Android NDK环境 D:\Android SDK\ndk\25.2.9519653 使用clang而不用gcc D:\Android SDK\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin\clang --version 查看是否安装成功clang ptrace 在 C 语言中&#xff0c;ptrace 已经被 Linux 内核实现&#xff0…

21款炫酷烟花合集

系列专栏 《Python趣味编程》《C/C趣味编程》《HTML趣味编程》《Java趣味编程》 写在前面 Python、C/C、HTML、Java等4种语言实现18款炫酷烟花的代码。 Python Python烟花① 完整代码&#xff1a;Python动漫烟花&#xff08;完整代码&#xff09; ​ Python烟花② 完整…