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

文章目录

  • 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就直接比较+版本号一起上。

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

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

相关文章

java学习part35List

155-集合框架-List接口常用方法的测试_哔哩哔哩_bilibili 1.List接口常用方法 2.不同实现类

Chart 3 OpenCL on Snapdragon

文章目录 前言3.1、Adreno GPU3.2、Adreno GPU 架构3.2.1、Adreno硬件架构在OpenCL方面的高层视图3.2.2、Waves and fibers3.2.3、 Latency hiding3.2.4 、L2缓存3.2.5、工作组分配3.2.6、Coalesced access&#xff08;合并访问&#xff09; 3.3 图形和计算负载之间的上下文切换…

如何做好一个软件开发项目经理?

要成为一名优秀的软件开发项目经理&#xff0c;需要具备一定的技术知识和管理能力。下面是学习和发展软件开发项目经理职业所需的关键能力和工作内容。 首先&#xff0c;作为软件开发项目经理&#xff0c;你需要具备扎实的软件开发知识和技能。这包括熟悉常用的编程语言、开发框…

springboot 整合 Spring Security 中篇(RBAC权限控制)

1.先了解RBAC 是什么 RBAC(Role-Based Access control) &#xff0c;也就是基于角色的权限分配解决方案 2.数据库读取用户信息和授权信息 1.上篇用户名好授权等信息都是从内存读取实际情况都是从数据库获取&#xff1b; 主要设计两个类 UserDetails和UserDetailsService 看下…

【Scopus检索】第六届生物技术与生物医学国际学术会议(ICBB 2024)

第六届生物技术与生物医学国际学术会议 2024 6th International Conference on Biotechnology and Biomedicine (ICBB 2024) 第六届生物技术与生物医学国际学术会议&#xff08;ICBB 2024&#xff09;将于2024年03月29日-3月31日在中国-武汉市召开。ICBB 2024将围绕“生物技术…

安装mysql数据库

1.1下载APT存储库&#xff08;下载链接&#xff09; 1.2安装APT存储库&#xff08;注意好正确的路径&#xff09; 将下载的文件传输到linux服务器对应目录下后执行以下命令&#xff1a; sudo dpkg -i mysql-apt-config_0.8.10-1_all.deb 选择mysql5.7 然后点击ok 然后执行 s…

vagrant up卡死问题

环境 OS&#xff1a;Windows 10Vagrant&#xff1a;Vagrant 2.2.14VirtualBox&#xff1a;6.1.18 r142142box&#xff1a;CentOS-7-x86_64-Vagrant-2004_01.VirtualBox.box 详细信息 解决办法 修改文件Vagrantfile&#xff0c;增加如下配置&#xff0c;测试可以正常启动 conf…

redis配置介绍

redis配置详解 一、redis.conf二、持久化1、RDB① 触发机制② 优缺点③ 恢复rdb 2、AOF① 优缺点② 恢复aof 三、发布订阅 一、redis.conf # -----NETWORK----- # 设置绑定ip bind 127.0.0.1 -::1 # 设置redis保护&#xff0c;只能通过绑定在本地回环地址上的网络接口进行访问…

股票代码合法验证:python字符串str应用

从键盘输入六位股票代码字符串&#xff0c;判定合法并输出板块分类&#xff0c;否则输出“NO”。 (笔记模板由python脚本于2023年12月04日 19:19:07创建&#xff0c;本篇笔记适合熟悉python字符串和字典的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https:…

C语言实现Berzier曲线几何作图算法

前言&#xff1a; 隐式曲线表达上的限制 计算上的多值性&#xff08;例如可能一个 x 对应多个 y&#xff09;存在导数 未定义的点坐标系进行变换后&#xff0c;曲线表达形式将可能会发生改变 一般的多项式幂基函数缺乏直观的几何意义 我们考虑由3个二元点对构造的二维平面曲…

使用正则表达式时-可能会导致性能下降的情况

目录 前言 正则表达式引擎 NFA自动机的回溯 解决方案 前言 正则表达式是一个用正则符号写出的公式&#xff0c;程序对这个公式进行语法分析&#xff0c;建立一个语法分析树&#xff0c;再根据这个分析树结合正则表达式的引擎生成执行程序(这个执行程序我们把它称作状态机&a…

6、原型模式(Prototype Pattern,不常用)

原型模式指通过调用原型实例的Clone方法或其他手段来创建对象。 原型模式属于创建型设计模式&#xff0c;它以当前对象为原型&#xff08;蓝本&#xff09;来创建另一个新的对象&#xff0c;而无须知道创建的细节。原型模式在Java中通常使用Clone技术实现&#xff0c;在JavaSc…

参加百度Apollo技术沙龙—感受自动驾驶的魅力

2023年12月2日下午2点&#xff0c;我有幸参加了百度Apollo技术沙龙&#xff0c;这是一个围绕Apollo新版本Beta的全面升级展开的深度交流活动。作为一名工程师&#xff0c;我深感荣幸能够与众多同行和专家一同探讨自动驾驶技术的快速发展 在这次沙龙中&#xff0c;我了解到Apo…

可编程电子负载原理是怎样的

可编程电子负载是一种模拟真实负载的电子设备&#xff0c;它可以模拟各种不同类型和规格的负载&#xff0c;如电阻、电容、电感等。通过调整电子负载的参数&#xff0c;可以实现对电源输出电压、电流、功率等性能指标的精确控制。可编程电子负载广泛应用于电源测试、电池充放电…

基于vue+node.js智慧校园学生办证系统

基于vuenode.js智慧校园学生办证系统 摘要&#xff1a;随着计算机技术和网络技术的飞快发展&#xff0c;它加速了国内信息化建设的进程&#xff0c;信息技术对管理改革产生了深远的影响。为了适应新时代的发展趋势&#xff0c;各行各业都高度重视信息化建设。在教育领域&#…

成为Java开发高手:掌握Spring框架的关键技能-DI

DI相关内容 1.1 setter注入1.1.2 注入引用数据类型1.1.3 注入简单数据类型步骤1:声明属性并提供setter方法步骤2:配置文件中进行注入配置步骤3:运行程序 1.2 构造器注入1.2.2 构造器注入引用数据类型步骤1:删除setter方法并提供构造方法步骤2:配置文件中进行配置构造方式注入步…

4.9 构建onnx结构模型-Equal

前言 构建onnx方式通常有两种&#xff1a; 1、通过代码转换成onnx结构&#xff0c;比如pytorch —> onnx 2、通过onnx 自定义结点&#xff0c;图&#xff0c;生成onnx结构 本文主要是简单学习和使用两种不同onnx结构&#xff0c; 下面以 Equal 结点进行分析 方式 方法一…

北京华联BHGMall“宠粉模式”不断迭代,强体验注互动成行业UP主

在今年双11热度遇冷后&#xff0c;双十二被官宣取消&#xff0c;而这背后本质已经间接印证&#xff1a;传统“电商大促”的模式&#xff0c;已经难以为继。反观线下消费市场&#xff0c;则是以持续恢复和增长成为经济恢复的亮点&#xff0c;从线下客流量的快速回升&#xff0c;…

软考2016年上半年第六题(适配器模式)与手术训练系统项目适配器模式的应用

软考2016年上半年第六题 public class Address {public void street(){System.out.println("a");};public void zip(){};public void city(){}; }package org.example.适配器模式;/*** 适配器模式&#xff08;Adapter Pattern&#xff09;是作为两个不兼容的接口之间…