JUC并发编程16 | CAS自旋锁

news2024/11/18 19:54:05

CAS自旋锁

是什么,干什么,解决了什么痛点?如何解决,如何使用。

原子类:java.util.concurrent.atomic

在没有CAS之前,多线程环境不使用原子类保证线程安全i++等操作,会出现数据问题,如果直接加锁synchronized,资源的开销就比较大

在出现CAS之后,多线程环境,使用原子类保证线程安全i++,类似我们的乐观锁

CAS是什么

CAS是compare and swap的缩写,中文翻译为比较并交换,实现并发算法时常用的一种技术

CAS 包含三个操作数 —— 内存位置、预期原值及更新值

在执行CAS操作的时候,将内存位置的值与预期原值比较,

  • 如果相匹配,那么处理器会自动将该位置值更新为新值
  • 如果不匹配,处理器不做任何操作,多个线程同时执行CAS只有一个会成功

CAS的原理

CAS 有三个操作数,位置内存值V,旧的预期值A,要修改的更新值为B

当且仅当就得预期值A与内存值V相同时,将内存值V修改位B,否则什么都不做,重来——即自旋

在这里插入图片描述

这是通过硬件级别保证的

Unsafe 类

CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性。

它是非阻塞的且自身具有原子性,也就是说这玩意效率更高且通过硬件保证,说明这玩意更可靠。

CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。

执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的,比起用synchronized重量级锁,这里的排他时间要短很多,所以在多线程情况下性能会比较好。

进入Unsafe方法查看源码

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

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

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

/**
上面三个方法都是类似的,主要对4个参数做一下说明。
var1:表示要操作的对象
var2:表示要操作对象中属性地址的偏移量
var4:表示需要修改数据的期望的值
var5/var6:表示需要修改为的新值
*/

1 Unsafe
是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地〈native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,共内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。

注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务

2 变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

// AtomicInteger 类
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

// Unsafe 类
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        // volatile 修饰,一旦var5被修改会被立即获知
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

3 变量value使用volatile修饰,保证了多线程之间的内存可见性

假设线程A和线程B两个线程同时执行getAndAddInt操作(分别跑在不同CPU上)

  1. AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存。
  2. 线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。
  3. 线程B也通过getlntVolatile(var1, var2)方法获取到value值3,此时刚好线程B没有被挂起并执行compareAndSwaplnt方法比较内存值也为3,成功修改内存值为4,线程B打完收工,一切OK。
  4. 这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值数字3和主内存的值数字4不一致,说明该值已经被其它线程抢先一步修改过了,那A线程本次修改失败,只能重新读取重新来一遍了。
  5. 线程A重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。

原子引用AtomicReference

public class CASDemo {
    public static void main(String[] args) {
        AtomicReference<User> atomicReference = new AtomicReference<>();
        User zhangsan = new User("zhangsan", 22);
        User lisi = new User("lisi", 24);
        atomicReference.set(zhangsan);
        System.out.println(atomicReference.compareAndSet(zhangsan,lisi)+"\t" + atomicReference.get().toString());
        System.out.println(atomicReference.compareAndSet(zhangsan,lisi)+"\t" + atomicReference.get().toString());
    }
}

CAS与自旋锁

通过cas操作完成自旋锁,A线程先进来,调用lock方法自己持有锁5秒;

B随后进来发现当前线程支持有所,进行自旋等待,直到A释放锁后B随后抢到

public class CASDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    public void lock(){
        Thread thread = Thread.currentThread();
        System.out.println("==============="+Thread.currentThread().getName()+" come in ==============");
        while (!atomicReference.compareAndSet(null,thread)) {}
    }
    public void unlock(){
        Thread thread =  Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println("==============="+Thread.currentThread().getName()+" task is over ==============");
    }
    public static void main(String[] args) throws InterruptedException {
        CASDemo casDemo = new CASDemo();
        new Thread(()->{
            casDemo.lock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                casDemo.unlock();
            }
        },"t1").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
            casDemo.lock();
            try {
                TimeUnit.SECONDS.sleep(8);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                casDemo.unlock();
            }
        },"t2").start();
    }
}

CAS的缺点

  • 循环时间长开销大
  • 具有ABA问题

循环时间长开销大

如果cas失败,会一直进行尝试。如果cas长时间一直不成功,可能会给cpu带来很大的开销

ABA问题

CAS会导致“ABA问题”。

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程1从内存位置V中取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作将值变成了B,然后线程2又将V位置的数据变成A,这时候线程1进行CAS操作发现内存中仍然是A,预期OK,然后线程1操作成功。

尽管线程1的CAS操作成功,但是不代表这个过程就是没有问题的。

@Data
@AllArgsConstructor
@NoArgsConstructor
class Book{
    private String name;
    private int id;
}
public class ABADemo {

    public static void main(String[] args) {

        Book java = new Book("java",1);
        Book mysql = new Book("mysql",2);
        AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(java, 1);
        new Thread(()->{
            // 初始条件是java
            System.out.println(stampedReference.getReference()+"\t初始条件是java:" + stampedReference.getStamp());
            // 此时邮戳莫有启动,但是已经被改为mysql了
            stampedReference.compareAndSet(java, mysql, stampedReference.getStamp(), stampedReference.getStamp());
            System.out.println(stampedReference.getReference()+"\t被改为mysql了" + stampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 修改回Java了
            stampedReference.compareAndSet(mysql, java, stampedReference.getStamp(), stampedReference.getStamp());
            System.out.println(stampedReference.getReference()+"\t修改回Java了:" + stampedReference.getStamp());
        },"t1").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 不知道被就该过了
            stampedReference.compareAndSet(java, mysql, stampedReference.getStamp(), stampedReference.getStamp());
            System.out.println(stampedReference.getReference()+"\t不知道被就该过了,此时还能改为mysql" + stampedReference.getStamp());

        },"t2").start();

    }
}
/**
Book(name=java, id=1)	初始条件是java:1
Book(name=mysql, id=2)	被改为mysql了1
Book(name=java, id=1)	修改回Java了:1
Book(name=mysql, id=2)	不知道被就该过了,此时还能改为mysql1
*/

解决:ABA

使用 AtomicStampedReference

内容版本
A1
B2
A3

解决代码

@Data
@AllArgsConstructor
@NoArgsConstructor
class Book{
    private String name;
    private int id;
}
public class ABADemo {
    public static void main(String[] args) {
        Book java = new Book("java",1);
        Book mysql = new Book("mysql",2);
        AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(java, 1);
        System.out.println(stampedReference.getReference()+"\t" + stampedReference.getStamp());
        boolean b;
        // 如果是java,且邮戳不变,那就换成mysql,同时邮戳+1
        b = stampedReference.compareAndSet(java, mysql, stampedReference.getStamp(), stampedReference.getStamp()+1);
        System.out.println(stampedReference.getReference()+"\t" + stampedReference.getStamp());
        // 把 java 换回来
        b = stampedReference.compareAndSet(mysql, java, stampedReference.getStamp(), stampedReference.getStamp()+1);
        System.out.println(stampedReference.getReference()+"\t" + stampedReference.getStamp());
    }
}

上面演示了单线程的情况,下面演示多线程的cas情况

@Data
@AllArgsConstructor
@NoArgsConstructor
class Book{
    private String name;
    private int id;
}
public class ABADemo {

    public static void main(String[] args) {

        Book java = new Book("java",1);
        Book mysql = new Book("mysql",2);
        AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(java, 1);
        new Thread(()->{
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t首次版本号:" + stampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 此时邮戳莫有启动,但是已经被改为mysql了
            stampedReference.compareAndSet(java, mysql, stampedReference.getStamp(), stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+stampedReference.getReference()+"\t版本号2:" + stampedReference.getStamp());

            // 修改回Java了
            stampedReference.compareAndSet(mysql, java, stampedReference.getStamp(), stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+stampedReference.getReference()+"\t版本号3:" + stampedReference.getStamp());
        },"t1").start();
        new Thread(()->{
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t首次版本号:" + stampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 被修改过了
            boolean b = stampedReference.compareAndSet(java, mysql, stamp, stampedReference.getStamp()+1);
            System.out.println(b+"\t"+stampedReference.getReference()+"\t" + stampedReference.getStamp());

        },"t2").start();
    }
}
/**

t1	首次版本号:1
t2	首次版本号:1
t1	Book(name=mysql, id=2)	版本号2:2
t1	Book(name=java, id=1)	版本号3:3
false	Book(name=java, id=1)	3
*/

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode特训 -- Week3 (字符串)

目录 字符串基础 字符串基本操作 字符串匹配算法 字符串异位词问题 分组分类问题和快速查找数据结构之间存在一定的关系。 字符串回文串问题 留下悬念&#xff1a;高级字符串算法题目(字符串 dp) 字符串基础 字符串定义&#xff1a;n个字符顺次排列而成的序列. 子串&…

MySQL好玩新特性:离线模式

GreatSQL社区原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注明来源。GreatSQL是MySQL的国产分支版本&#xff0c;使用上与MySQL一致。作者&#xff1a;Yejinrong/叶金荣文章来源&#xff1a;GreatSQL社区原创 继续吹MySQL 8.0~ 在以前&#xff0c;当需要对MySQL数…

CVPR 2023 | VoxelNeXt实现全稀疏3D检测跟踪,还能结合Seg Anything

在本文中&#xff0c;研究者提出了一个完全稀疏且以体素为基础的3D物体检测和跟踪框架VoxelNeXt。它采用简单的技术&#xff0c;运行快速&#xff0c;没有太多额外的成本&#xff0c;并且可以在没有NMS后处理的情况下以优雅的方式工作。VoxelNeXt在大规模数据集nuScenes、Waymo…

《编程思维与实践》1064.A-B(Big Integer)

《编程思维与实践》1064.A-B(Big Integer) 题目 思路 两个大整数做减法有可能出现结果为负的情况,因此结构体BIGINT需要补充符号位sign, 因为减法是个位对齐进行操作,为了方便起见,本题还是采用逆序(个位开始)存储. 注意到本题的两个整数均非负,所以不需要考虑转化为加法的情况…

TOOM舆情监测系统:从原理到应用

舆情监测系统是一种可以帮助企业、政府等机构了解公众对自己的看法和态度&#xff0c;提前发现和预测可能出现的危机或负面舆情&#xff0c;从而进行预警和应对的工具。本文将从原理到应用&#xff0c;通过国内具体案例分析&#xff0c;探讨舆情监测系统的相关知识。 一、舆情…

DSP:数字信号处理的原理及应用

什么是DSP&#xff1f;DSP一般有两种解释&#xff1a; 1、Digital Signal Processing&#xff0c;数字信号处理技术&#xff0c;简称DSP。是一门涉及许多学科而又广泛应用于许多领域的新兴学科。数字信号处理是围绕着数字信号处理的理论、实现和应用等几个方面发展起来的。数字…

有哪些好用的AI工具?

现在有很多好用的AI工具&#xff0c;以下是一些常用的&#xff1a; 1. TensorFlow&#xff1a;谷歌开发的深度学习框架&#xff0c;支持多种编程语言&#xff0c;包括Python、C、Java等。 2. PyTorch&#xff1a;Facebook开发的深度学习框架&#xff0c;易于使用&#xff0c;…

干货分享:PCB防静电设计的必要性

平时通过走路穿衣等日常活动带来的摩擦&#xff0c;会产生不同幅值的静电电压&#xff0c;但其能量很小不会对人体产生伤害&#xff0c;不过对于电子元器件来说&#xff0c;这种静电能量却是不能忽视的。 在干燥的环境下&#xff0c;人体静电&#xff08;ESD&#xff09;的电压…

matlab实验二可视化

学聪明点&#xff0c;自己改&#xff0c;别把我卖了 一、实验目的及要求 要求 1、掌握 MATLAB常用的二维和三维绘图函数 2、掌握MATLAB的图形注释 3、熟悉MATLAB常用的图形修饰 4、熟悉MATLAB的图形动画 实验原理 1、MATLAB二维绘图&#xff1a;plot,fplot,fimplicit&#xf…

Matlab 非线性迭代法(2)高斯牛顿法

一、思想 高斯牛顿法的对象是最小二乘法。 采用一定的方法对Hession 矩阵进行近似&#xff0c;这样的话可以减少计算量&#xff0c;只需要计算一阶偏导数得到雅可比矩阵即可。 minF(x)|| f(x)||^2 那么x在xk处的增量Δxk出的最小二乘法为 minF(xkΔxk)∣∣f(xk​Δxk​)∣…

Word处理控件Aspose.Words功能演示:使用 Java 处理 Word 文档的原始版本或修订版本

Aspose.Words是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。 Aspose API支持流行文件格式处理&#xff0c;并…

企业电子招投标采购系统源码之登录页面-java spring cloud

​ 信息数智化招采系统 服务框架&#xff1a;Spring Cloud、Spring Boot2、Mybatis、OAuth2、Security 前端架构&#xff1a;VUE、Uniapp、Layui、Bootstrap、H5、CSS3 涉及技术&#xff1a;Eureka、Config、Zuul、OAuth2、Security、OSS、Turbine、Zipkin、Feign、Monitor、…

制造业为什么要数字化?有何意义?

制造业为什么要数字化&#xff1f;有何意义&#xff1f; 党的二十大报告指出&#xff0c;要“坚持把发展经济的着力点放在实体经济上&#xff0c;推进新型工业化”“促进数字经济和实体经济深度融合”。 新一代信息技术催生第四次工业革命&#xff0c;互联网、大数据、人工智能…

MISC:图片隐写的破解方法.

MISC&#xff1a;图片隐写的破解方法. Misc即杂项&#xff0c;是信息隐藏又称信息伪装&#xff0c;就是通过减少载体的某种冗余&#xff0c;如空间冗余、数据冗余等&#xff0c;来隐藏敏感信息&#xff0c;达到某种特殊的目的。 信息隐藏打破了传统密码学的思维范畴&#xff0…

软件开发企业如何合理解决企业增值税和所得税问题?

业务是流程&#xff0c;财税是结果&#xff0c;税收问题千千万&#xff0c;关注《税算盘》来帮你找答案。 软件开发是根据用户的要求建造出软件系统或者系统中的软件部分的过程&#xff0c;软件开发是一项包括需求捕捉、需求分析、设计、实现和测试的系统工程。 与传统产业的…

实现高并发秒杀的 7 种方式

1.引言 高并发场景在现场的日常工作中很常见&#xff0c;特别是在互联网公司中&#xff0c;这篇文章就来通过秒杀商品来模拟高并发的场景。文章末尾会附上文章的所有代码、脚本和测试用例。 本文环境&#xff1a; SpringBoot 2.5.7 MySQL 8.0 X MybatisPlus Swagger2.9.2 …

【GPT-4理论系列篇】GPT-4核心技术探秘 | 京东云技术团队

作者&#xff1a;京东零售 刘岩 前言 GPT-4已经发布有一段时间了&#xff0c;但是出于安全性等各种原因&#xff0c;OpenAI并没有公布GPT-4的技术细节和代码&#xff0c;而是仅仅给出了一个长达100页的技术报告[1]。这个技术报告着重介绍了GPT-4的强大之处&#xff0c;仅仅给…

分享Python采集66个html5代码,总有一款适合您

分享Python采集66个html5代码&#xff0c;总有一款适合您 Python采集的66个html5代码下载链接&#xff1a;https://pan.baidu.com/s/1z0AsahthBcpDECA1bVg5ZA?pwd168a 提取码&#xff1a;168a 基于canvas的背景颜色渐变动画插件 jquery音乐播放器插件jsRapAudio HTML5 sv…

SpringBoot整合ES,ik分词器

"reason": "Failed to parse mapping: analyzer [ik_max_word] has not been configured in mappings" 这是因为没有安装ES的IK分词器 下载地址 Release v8.7.0 medcl/elasticsearch-analysis-ik GitHub ElasticSearch 内置了分词器&#xff0c;如标准…

分享Python采集240个jQuery代码,总有一款适合您

分享Python采集240个jQuery代码&#xff0c;总有一款适合您 Python采集的240个jQuery代码下载链接&#xff1a;https://pan.baidu.com/s/1S_lV6aoXOtohc3zk3r3uxA?pwd2h4r 提取码&#xff1a;2h4r jQuery实时搜索插件-HideSeek ps样式的jQuery颜色选择插件 colpick-jQuer…