【JUC】二十一、CAS比较并交换

news2025/1/21 21:37:03

文章目录

  • 1、初体验
  • 2、CAS概述
  • 3、Unsafe类
  • 4、Unsafe汇编
  • 5、原子引用AutomicReference
  • 6、手写自旋锁SpinLock
  • 7、CAS的两大缺点
  • 8、AtomicStampedReference类解决ABA问题

1、初体验

没有CAS时,多线程环境下不使用原子类保证线程安全,比如i++,可以synchronized搭配volatile:

public class Counter{

	private volatile int vlaue = 0;   //volatile

	public int getValue(){  //利用volatile可见性保证并发下也能读取到最新值
		return value;
	}
	
	public synchronized int incerment(){  //利用synchronized保证复合操作的原子性
		return value++;
	}
}

使用CAS,多线程下使用原子类保证线程安全,还是进行i++:

AtomicInteger atomicInteger = new AtomicInteger();

public int getAtomicInteger(){
    return atomicInteger.get();
}

public int setAtomicInteger(){
    return atomicInteger.getAndIncrement();  //i++
}

Atomiclnteger 类主要利用 CAS (compare and swap)+ volatle 和 native 方法来保证原子操作,从而避免 synchronized 的高开销(重量级锁),执行效率大为提升。

2、CAS概述

CAS,即Compare And Swap的缩写,译:比较并交换,实现并发算法时常用到的一种技术。

它包含三个操作数:内存位置、预期原值及更新值。执行CAS操作的时候,将内存位置的值与预期原值比较:

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

CAS有3个操作数,位置内存值V,旧的预期值A,要修改的更新值B当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来一次,这种反复重试的行为就叫自旋

在这里插入图片描述

t1线程从主内存读到i=5,+1后准备把6写回主内存,此时,比较期望值5和内存中的实际i值,若相等,则乐观的认为自己运算的期间没有其他线程修改i,就将i写回主内存。反之,比如主内存i=6,那t1就再来一次,i=6,i+1,期望6,此时如果主内存i=6,则写回成功,反之继续自旋。
在这里插入图片描述

简单说CAS就是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。

3、Unsafe类

AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5,2023) + "\t current value: " + atomicInteger.get());
//此时,已改为2023,再预期5自然修改失败
System.out.println(atomicInteger.compareAndSet(5,2023) + "\t current value: " + atomicInteger.get());

此时只有一个main线程,没有其他线程争抢,预期值正确,CAS第一次就成功:

在这里插入图片描述

compareAndSet方法源码:

在这里插入图片描述

其中U为Unsafe对象,继续往下跟是调用Unsafe对象的方法源码:

在这里插入图片描述

关于Unsafe类:

原子类靠的CAS思想,CAS思想则又是依靠Unsafe类中的各个native方法来落地实现的。

Unsafe类是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native) 方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存(jdk/jre/rt.jar/sun/misc/Unsafe.class)。注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用作系统底层资源执行相应任务

AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger .getAndIncrement();

跟进方法:

在这里插入图片描述
在这里插入图片描述

do-while实现更新失败时继续自旋,var1即this,var2即内存地址偏移量,var5即期望值,var5+var4即更新值,在这里的传值就是当内存值和期望值不相等时加1,然后继续循环。

4、Unsafe汇编

在这里插入图片描述

new AtomicInteger(3).getAndIncrement();

在这里插入图片描述

上面的源码再往下到native方法:

在这里插入图片描述

该本地方法,实现于unsafe.app:

在这里插入图片描述

在这里插入图片描述
以Window10为例:

在这里插入图片描述
在这里插入图片描述

CAS对应在底层是CPU的一条原子指令cmpxchg,执行cmpxchg指令时,先判断操作系统是否为多核,是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后执行CAS操作,也就是说CAS的原子性是CPU实现独占的,相比synchronized,它的排他时间更短,因此性能比synchronized更优。CAS这种非阻塞的原子性操作,是通过硬件保证了比较-更新的原子性。

原语,操作系统用语,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断

CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法,调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语。

总之:

CAS是靠硬件实现的,从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性,实现方式是基于硬件平台的汇编指令,在intel的CPU中(X86机器上),使用的是汇编指令cmpxchg指令。

在这里插入图片描述

CAS核心思想就是:比较要更新变量的值V和预期值E (compare),相等才会将V的值设为新值N (swap),如果不相等自旋再来。

5、原子引用AutomicReference

AtomicInteger是原子整型,可否有其它自定义类型的原子类型,比如AtomicBook ⇒ AutomicReference<T>

@Data
@AllArgsConstructor
class User{

    String userName;

    int age;
}

public class AtomicDemo2 {

    public static void main(String[] args) {
        AtomicReference<User> atomicReference = new AtomicReference<>();
        User z3 = new User("z3",22);
        User li4 = new User("li4",23);
        atomicReference.set(z3);
        //修改成功
        System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
        //期望失败
        System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());

    }
}

期望是张三User,是则改为李四,改为李四后再期望张三,本次set失败:

在这里插入图片描述

6、手写自旋锁SpinLock

不用synchronized,用自旋思想完成加锁:

public class SpinLockDemo {

    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    public void lock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t" + "----come in");
        //自旋,必须当前原子引用类对象值为null,即没有其他线程占用,才抢锁成功
        while(!atomicReference.compareAndSet(null,thread)){

        }
    }

    public void unlock(){
        Thread thread = Thread.currentThread();
        //期望值为当前线程,改为null
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName() + "\t" + "----over,unlock...");
    }

    
}

通过CAS操作完成自锁,A线程先进来调ImyLock 方法自已持有锁5 秒钟,B随后进来后发现当前有线程持有锁,所以只能通过自旋等待,直到A释放锁后B随后抢到。

public static void main(String[] args) throws InterruptedException {
    SpinLockDemo spinLockDemo = new SpinLockDemo();
    new Thread(() -> {
        spinLockDemo.lock();
        //5s释放锁
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        spinLockDemo.unlock();
    },"A").start();
    //休眠200ms,让A线程优先于B启动
    Thread.sleep(200);
    new Thread(() -> {
        spinLockDemo.lock();
        spinLockDemo.unlock();
    },"B").start();
}

在这里插入图片描述

7、CAS的两大缺点

CAS落地后,在多线程下能性能优于synchronized的完成任务,但也有缺点:

  • 循环时间长会导致开销很大
  • ABA问题

在这里插入图片描述

ABA问题,比如说一个线程1从内存位置V中取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作将值变成了B,然后线程2又将V位置的数据变成A,这时候线程1进行CAS操作发现内存中仍然是A,预期OK,然后线程1操作成功。尽管线程1的CAS操作成功,但是不代表这个过程就是没有问题的。解决ABA问题的思路就是使用戳记流水。

在这里插入图片描述

ABA的demo:

public class ABA {
    static AtomicInteger atomicInteger = new AtomicInteger(100);

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,101);
            try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
            //A线程再B线程比较内存值前,又改回了100
            atomicInteger.compareAndSet(101,100);
        },"A").start();
        //休眠,保证ABA出現
        Thread.sleep(300);
        new Thread(() -> {
            //虽然中间被A改过两次,但这里比较内存值仍能成功
            System.out.println(atomicInteger.compareAndSet(100, 200) + "\t" + atomicInteger.get());
        },"B").start();
    }
}

在这里插入图片描述

8、AtomicStampedReference类解决ABA问题

public class ABA {
    static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号:" + stamp);
            //暂停500ms,以确保让B线程拿到的初始版本号和A线程一样
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName() + "\t" + "二次版本号:" + stampedReference.getStamp());
            stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName() + "\t" + "三次版本号:" + stampedReference.getStamp());
        },"A").start();
        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            //获取完时间戳后,等待2s,保证能发生ABA
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号:" + stamp);
            boolean result = stampedReference.compareAndSet(100, 200, stamp, stamp + 1);
            System.out.println(result + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp());
        },"B").start();
    }
}

此时B线程期望的时间戳为放开始的stamp,自然修改失败,ABA成功解决:

在这里插入图片描述

总之,解决ABA就直接比较+版本号一起上。

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

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

相关文章

21章网络通信

21.1——网络程序设计基础 网络程序设计编写得到是与其他计算机进行通信的程序 21.1.1——局域网与互联网 为了实现两台计算机的通信&#xff0c;必须用一个网络线路连接两台计算机 21.1.2——网络协议 网络协议规定了计算机之间连接的物理、机械 (网线与网卡的连接规定)、…

云上守沪 | 云轴科技ZStack成功实践精选(上海)

为打造国际数字之都&#xff0c;上海发布数字经济发展“十四五”规划&#xff0c;围绕数字新产业、数据新要素、数字新基建、智能新终端等重点领域&#xff0c;加强数据、技术、企业、空间载体等关键要素协同联动&#xff0c;加快进行数字经济发展布局&#xff1b;加快基础软件…

Linux环境下的MySQL安装

文章目录 前提说明1.卸载内置环境2.检查系统安装包3.卸载这些默认安装包4.获取MySQL官方yum源5.安装MySQLyum源&#xff0c;对比前后yum源6.查看yum源是否生效7.安装MySQL服务8.查看相对应的配置文件9.启动服务10.查看启动服务11.登录方法一12.登录方法二13.登录方法三14.设置开…

护理简历自我评价15篇

自我评价示例1&#xff1a; 我性格开朗&#xff0c;上进心强&#xff0c;做事不马虎&#xff0c;有良好的思想道德&#xff0c;注重集体荣誉感。我具备强大的护理技能和团队协作精神&#xff0c;能够在高压环境下保持冷静&#xff0c;积极应对挑战。我期待着在医疗领域发挥我的…

MySQL数据库与其管理工具Navicat

这里介绍MySQL数据库和Navicat的使用 1.下载MySQL数据库及MySQL客户端管理工具Navicat 登录www.mysql.com下载MySQL 登录www.navicat.com.cn/download下载客户端管理工具 2.启动MySQL数据库服务器 以管理员身份打开命令提示窗口 找到mysql的bin目录 输入初始化命令mysqld…

认识jmeter接口测试工具!

jmeter简介 Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试&#xff0c;它最初被设计用于Web应用测试&#xff0c;但后来扩展到其他测试领域。 下载 下载地址&#xff1a;Apache JMeter - Download Apache JMeter 安装 由于Jmeter是基于Java的…

springboot 整合 Spring Security+JWT 实现token 认证和校验

1.大概是这个样子 JWT 是什么&#xff1f; Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准&#xff08;(RFC 7519).该token被设计为紧凑且安全的&#xff0c;特别适用于分布式站点的单点登录&#xff08;SSO&#xff09;场景。JWT的声明…

设计原则 | 依赖转置原则

一、依赖转置原则&#xff08;DIP&#xff1a;Dependence Inversion Principle&#xff09; 1、原理 高层模块不应该依赖低层模块&#xff0c;二者都应该依赖于抽象抽象不应该依赖于细节&#xff0c;细节应该依赖于抽象 2、层次化 Booch曾经说过&#xff1a;所有结构良好的面…

HTML5+CSS3+JS小实例:数字滑动选择控件

实例:数字滑动选择控件 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport" content=&quo…

【数据结构(八)】哈希表

文章目录 1. 基本概念1.1. 哈希表基本介绍 2. 实例应用2.1. 思路分析2.2. 代码实现2.2.1. 实现添加、显示功能2.2.2. 实现查找功能 1. 基本概念 先看一个实际需求&#xff1a; google 公司的一个上机题&#xff1a;     有一个公司&#xff0c;当有新的员工来报道时&…

Centos7部署NFS服务

搭建NFS存储服务器--基于CentOS7系统 - jianmuzi - 博客园 在CentOS中搭建NFS - 陌上荼靡 - 博客园 NFS简介 NFS 是 Network FileSystem 的缩写&#xff0c;顾名思义就是网络文件存储系统&#xff0c;它最早是由 Sun 公司发展出来的&#xff0c;也是 FreeBSD 支持的文件系统…

6.1810: Operating System Engineering 2023 <Lab3: page tables>

一、本节任务 实验环境&#xff1a; 二、要点 如何防止程序破坏内核或其他进程空间&#xff1f;隔离地址空间&#xff0c;进程只能读写自己的内存空间。 在保证隔离的同时&#xff0c;如何将多个地址空间复用到一个物理内存上&#xff1f;虚拟内存/页表。操作系统通过页表来为…

BUUCTF 加固题 babypython WriteUp

原题wp参考链接&#xff1a;https://www.cnblogs.com/karsa/p/13529769.html 这是CISCN2021 总决赛的题&#xff0c;解题思路是软链接zip 读取文件&#xff0c;然后伪造admin的session读取flag 回到buuctf的这个题&#xff1a; ssh连上去&#xff0c;查看 文件 /app/y0u_fou…

分割算法-大津算法

分割算法-大津算法 一、什么是大津算法二、算法原理三、公式推导四、代码五、算法适用性 大津算法介绍以及C函数代码实现。 一、什么是大津算法 大津算法&#xff08;Otsu&#xff09;由日本学者大津展之在1979年提出&#xff0c;又称最大类间方差法。此法求得的阈值&#xff…

Lidar-SLAM的历史与现状

文章&#xff1a;LiDAR-based SLAM for robotic mapping: state of the art and new frontiers 作者&#xff1a;Xiangdi Yue and Miaolei He 编辑&#xff1a;点云PCL 欢迎各位加入知识星球&#xff0c;获取PDF论文&#xff0c;欢迎转发朋友圈。文章仅做学术分享&#xff0c…

MOS管加三个元件就组成BUCK电路,为何说难点在于电感?

只要是电子产品就需要供电&#xff0c;就离不开电源&#xff0c;那什么是电源&#xff1a;小到手表中的电子&#xff0c;遥控器的电源&#xff0c;大到220V家庭用电&#xff0c;都可以看做是电源。然而在我们的电路设计中&#xff0c;会用到各种芯片&#xff0c;各种芯片所需要…

什么是呼叫中心的语音通道?呼叫中心语音线路有几种?

什么是呼叫中心的语音通道&#xff1f; 呼叫中心的语音通道是指在呼叫中心中使用的语音信号传输通道&#xff0c;它是呼叫中心中至关重要的一部分&#xff0c;负责将客户的语音信息传递给客服代表&#xff0c;以及将客服代表的语音信息传递给客户。在呼叫中心的运营中&#xf…

探索鸿蒙 DevEcoStudio汉化+运行报错

在下载好软件&#xff0c;摸索着成功创建了一个项目的时候&#xff0c;点击运行&#xff0c;竟然失败了。而且一大堆的英文也不知道从何入手&#xff0c;从网上搜了一下&#xff0c;找到了汉化的办法&#xff0c;并且解决了问题。我这里走的是Mac的步骤&#xff0c;微软的其实一…

软件科技成果鉴定测试需提供哪些材料?

为了有效评估科技成果的质量&#xff0c;促进科技理论向实际应用转化&#xff0c;所以需要进行科技成果鉴定测试。申请鉴定的科技成果范围是指列入国家和省、自治区、直辖市以及国务院有关部门科技计划内的应用技术成果&#xff0c;以及少数科技计划外的重大应用技术成果。   …

LIO-SAM如何存储地图

1. 需要修改配置文件config/params.yaml文件的参数&#xff1a; savePCD: true # https://github.com/TixiaoShan/LIO-SAM/issues/3 savePCDDirectory: "/zoe/ws_lio_sam/src/LIO-SAM/map" 2.保存地图&#xff1a; source deve…