Java CAS操作

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

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操作,确保是线程安全的

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    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;

    /**
     * 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使用例子

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()

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()

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

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

即:

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

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

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

unsafe线程安全操作例子程序

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,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。

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;
    }

    @Override
    public 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问题
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 A
            System.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问题的解决(加上版本)
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 A
            System.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
t1 1
t2 1
t1 true 2
t1 true 3
t2 false 3

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/961971.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.25 视觉风暴:NumPy驱动数据可视化

1.25 视觉风暴&#xff1a;NumPy驱动数据可视化 目录 #mermaid-svg-i3nKPm64ZuQ9UcNI {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-i3nKPm64ZuQ9UcNI .error-icon{fill:#552222;}#mermaid-svg-i3nKPm64ZuQ9UcNI …

鸟瞰欧洲(意境欧洲) 第一季

目录 《鸟瞰欧洲 第一季》纪录片笔记一、基本信息二、详细内容&#xff08;一&#xff09;剧集设置&#xff08;二&#xff09;各国亮点1. **荷兰**2. **意大利**3. **德国**4. **英国**5. **西班牙**6. **波兰** &#xff08;三&#xff09;拍摄特色 三、特色与评价四、总结五…

【MQ】探索 Kafka

高性能 消息的顺序性、顺序写磁盘 零拷贝 RocketMQ内部主要是使用基于mmap实现的零拷贝&#xff0c;用来读写文件 减少cpu的拷贝次数和上下文切换次数&#xff0c;实现文件的高效读写操作 Kafka 零拷贝 Kafka 使用到了 mmap 和 sendfile 的方式来实现零拷贝。分别对应 Jav…

供应链系统设计-供应链中台系统设计(十一)- 清结算中心概念片篇

概述 上篇供应链系统设计-供应链中台系统设计&#xff08;十&#xff09;- 清结算中心概念片篇文中提到了什么是金融客户、资金账号、资金账户、以及资金账号和资金账户的关系&#xff0c;如下图所示&#xff1a; 这些对于清算和结算来说都是前置的概念&#xff0c;本篇文章我…

allegro修改封闭图形线宽

说在前面 我们先把最优解说在前面,然后后面再说如果当时不熟悉软件的时候为了挖孔是用了shapes该怎么修改回来。 挖空最方便的方式是在cutout层画一个圆弧,下面开始图解,先add一个圆弧 z 最好是在画的时候就选择好层,如果忘记了后续再换回去也行,但好像软件有bug,此处并…

使用scikit-learn中的KNN包实现对鸢尾花数据集的预测

引言 K最近邻&#xff08;KNN&#xff09;算法是一种简单且直观的分类算法。它通过计算数据点之间的距离来对新样本进行分类。鸢尾花数据集是一个经典的机器学习数据集&#xff0c;包含了三种不同类型的鸢尾花&#xff0c;每种类型由四个特征&#xff08;花萼长度、花萼宽度、…

Hive:静态分区(分区语法,多级分区,分区的查看修改增加删除)

hive在建表时引入了partition概念。即在建表时&#xff0c;将整个表存储在不同的子目录中&#xff0c;每一个子目录对应一个分区。在查询时&#xff0c;我们就可以指定分区查询&#xff0c;避免了hive做全表扫描&#xff0c;从而提高查询率。 oracle和Hive分区的区别 orcale在…

基于FPGA的BT656解码

概述 BT656全称为“ITU-R BT.656-4”或简称“BT656”,是一种用于数字视频传输的接口标准。它规定了数字视频信号的编码方式、传输格式以及接口电气特性。在物理层面上,BT656接口通常包含10根线(在某些应用中可能略有不同,但标准配置为10根)。这些线分别用于传输视频数据、…

随机矩阵投影长度保持引理及其证明

原论文中的引理 2 \textbf{2} 2 引理 2 \textbf{2} 2的内容​​ &#x1f449;前提 1 1 1&#xff1a;设一个随机矩阵 S ( s i j ) ∈ R t d S\text{}(s_{ij})\text{∈}\mathbb{R}^{t\text{}d} S(sij​)∈Rtd&#xff0c;每个元素 s i j s_{ij} sij​独立同分布于 N ( 0 , …

详细解释java当中的所有知识点(前言及数据类型及变量)(第一部分)

会将java当中的所有的知识点以及相关的题目进行分享&#xff0c;这是其中的第一部分&#xff0c;用红色字体标注出重点&#xff0c;以及加粗的方式进行提醒 目录 一、Java语言概述 1.Java语言简介 2.语言优势 二、main方法 1.Java程序结构组成 2.运行Java程序 3.注释 4.…

STM32 PWMI模式测频率占空比

接线图&#xff1a; PWMI基本结构 代码配置&#xff1a; 与上一章输入捕获代码一样&#xff0c;根据结构体&#xff0c;需要在输入捕获单元再配置一个通道。我们调用一个函数 这个函数可以给结构体赋值&#xff0c;当我们定义了一遍结构体参数&#xff0c;再调用这个函数&…

Fort Firewall:全方位守护网络安全

Fort Firewall是一款专为 Windows 操作系统设计的开源防火墙工具&#xff0c;旨在为用户提供全面的网络安全保护。它基于 Windows 过滤平台&#xff08;WFP&#xff09;&#xff0c;能够与系统无缝集成&#xff0c;确保高效的网络流量管理和安全防护。该软件支持实时监控网络流…

Baklib引领内容管理平台新时代优化创作流程与团队协作

内容概要 在迅速变化的数字化时代&#xff0c;内容管理平台已成为各种行业中不可或缺的工具。通过系统化的管理&#xff0c;用户能够有效地组织、存储和共享信息&#xff0c;从而提升工作效率和创意表达。Baklib作为一款新兴的内容管理平台&#xff0c;以其独特的优势和创新功…

【算法设计与分析】实验2:递归与分治—Hanoi塔、棋盘覆盖、最大子段和

目录 一、实验目的 二、实验环境 三、实验内容 四、核心代码 五、记录与处理 六、思考与总结 七、完整报告和成果文件提取链接 一、实验目的 掌握递归求解问题的思想及对应的程序编码结构。针对不同的问题&#xff0c;能够利用递归进行问题求解&#xff0c;并利用Jav…

mysql_init和mysql_real_connect的形象化认识

解析总结 1. mysql_init 的作用 mysql_init 用于初始化一个 MYSQL 结构体&#xff0c;为后续数据库连接和操作做准备。该结构体存储连接配置及状态信息&#xff0c;是 MySQL C API 的核心句柄。 示例&#xff1a; MYSQL *conn mysql_init(NULL); // 初始化连接句柄2. mysql_…

C语言------数组从入门到精通

1.一维数组 目标:通过思维导图了解学习一维数组的核心知识点: 1.1定义 使用 类型名 数组名[数组长度]; 定义数组。 // 示例&#xff1a; int arr[5]; 1.2一维数组初始化 数组的初始化可以分为静态初始化和动态初始化两种方式。 它们的主要区别在于初始化的时机和内存分配的方…

留学毕业论文如何利用不同问题设计问卷

在留学毕业论文的写作中&#xff0c;我们经常会遇到各种问题&#xff0c;例如选择合适的问题&#xff0c;选择合适的研究方法&#xff0c;以及设计合理的研究过程。然而在完成留学毕业论文的过程中&#xff0c;我们往往会在研究设计这里卡住。即使我们选准了研究问题和研究方法…

范冰冰担任第75届柏林电影节主竞赛单元评委 共鉴电影佳作

近日&#xff0c;备受瞩目的柏林电影节迎来了新一届盛事&#xff0c;而华人演员范冰冰将以主竞赛单元评委身份亮相&#xff0c;引发了广泛关注。此前她已担任过戛纳国际电影节、东京国际电影节、圣塞巴斯蒂安国际电影节等众多电影节主竞赛单元评委。作为国际影坛的知名人物&…

对顾客行为的数据分析:融入2+1链动模式、AI智能名片与S2B2C商城小程序的新视角

摘要&#xff1a;随着互联网技术的飞速发展&#xff0c;企业与顾客之间的交互方式变得日益多样化&#xff0c;移动设备、社交媒体、门店、电子商务网站等交互点应运而生。这些交互点不仅为顾客提供了便捷的服务体验&#xff0c;同时也为企业积累了大量的顾客行为数据。本文旨在…

如何用 Groq API 免费使用 DeepSeek-R1 70B,并通过 Deno 实现国内访问

这几天都被Deepseek刷屏了&#xff0c;而且Deepseek由于异常访问量&#xff0c;这几天都不能愉快的和它玩耍了&#xff0c; 我发现Groq新增了一个Deepseek的70b参数的模型&#xff0c; DeepSeek-R1 70B 作为一款强大的开源模型&#xff0c;提供了卓越的推理能力&#xff0c;而 …