并发编程07:CAS

news2024/9/27 9:23:26

文章目录

  • 7.1 原子类
  • 7.2 没有CAS之前
  • 7.3 使用CAS之后
  • 7.4 是什么?
  • 7.5 CAS底层原理?谈谈对Unsafe类的理解?
    • 7.5.1 Unsafe
    • 7.5.2 源码分析
    • 7.5.3 底层汇编
  • 7.6 原子引用
  • 7.7 CAS与自旋锁,借鉴CAS思想
    • 7.7.1 是什么?
    • 7.7.2 自己实现一个自旋锁spinLockDemo
  • 7.8 CAS缺点
    • 7.8.1 循环时间长开销很大
    • 7.8.2 引出来ABA问题?

7.1 原子类

Java.util.concurrent.atomic

在这里插入图片描述

7.2 没有CAS之前

多线程环境中不使用原子类保证线程安全i++(基本数据类型)

class Test {
        private volatile int count = 0;
        //若要线程安全执行执行count++,需要加锁
        public synchronized void increment() {
                  count++;
        }

        public int getCount() {
                  return count;
        }
}

7.3 使用CAS之后

多线程环境中使用原子类保证线程安全i++(基本数据类型)---------->类似于乐观锁

class Test2 {
        private AtomicInteger count = new AtomicInteger();

        public void increment() {
                  count.incrementAndGet();
        }
      //使用AtomicInteger之后,不需要加锁,也可以实现线程安全。
       public int getCount() {
                return count.get();
        }
}

7.4 是什么?

CAS(compare and swap),中文翻译为比较并交换,实现并发算法时常用到的一种技术,用于保证共享变量的原子性更新,它包含三个操作数—内存位置、预期原值与更新值。
执行CAS操作的时候,将内存位置的值与预期原值进行比较:

  • 如果相匹配,那么处理器会自动将该位置更新为新值
  • 如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。
    在这里插入图片描述
public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        System.out.println(atomicInteger.compareAndSet(5, 2022) + "\t" + atomicInteger.get());//true	2022
        System.out.println(atomicInteger.compareAndSet(5, 2022) + "\t" + atomicInteger.get());//false	2022
    }
}

compareAndSet 源码
在这里插入图片描述

7.5 CAS底层原理?谈谈对Unsafe类的理解?

7.5.1 Unsafe

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

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

问题:我们知道i++是线程不安全的,那AtomicInteger.getAndIncrement()如何保证原子性?
AtomicInteger类主要利用CAS+volatile和native方法来保证原子操作,从而避免synchronized的高开销,执行效率大为提升:
在这里插入图片描述

CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

7.5.2 源码分析

在这里插入图片描述

7.5.3 底层汇编

在这里插入图片描述

JDK提供的CAS机制,在汇编层级会禁止变量两侧的指令优化,然后使用compxchg指令比较并更新变量值(原子性)
总结:

  • CAS是靠硬件实现的从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性
  • 实现方式是基于硬件平台的汇编指令,在inter的CPU中,使用的是汇编指令compxchg指令
  • 核心思想就是比较要更新变量V的值和预期值E,相等才会将V的值设为新值N,如果不相等自旋再来

7.6 原子引用

在这里插入图片描述

@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
    String userName;
    int age;
}

public class AtomicReferenceDemo {
    public static void main(String[] args) {
        AtomicReference<User> atomicReference = new AtomicReference<>();
        User z3 = new User("z3", 22);
        User li4 = new User("li4", 25);

        atomicReference.set(z3);
        System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());//true	User(userName=li4, age=25)
        System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());//false	User(userName=li4, age=25)

    }
}

7.7 CAS与自旋锁,借鉴CAS思想

7.7.1 是什么?

CAS是实现自旋锁的基础,CAS利用CPU指令保证了操作的原子性,以达到锁的效果,至于自旋锁—字面意思自己旋转。是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

7.7.2 自己实现一个自旋锁spinLockDemo

题目:实现一个自旋锁,借鉴CAS思想
通过CAS完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B随后进来后发现当前有线程持有锁,所以只能通过自旋等待,直到A释放锁后B随后抢到。

public class SpinLockDemo {

    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void lock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t --------come in");
        while (!atomicReference.compareAndSet(null, thread)) {

        }
    }

    public void unLock() {
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\t --------task over,unLock.........");
    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();
        new Thread(() -> {
            spinLockDemo.lock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.unLock();
        }, "A").start();


        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            spinLockDemo.lock();
            spinLockDemo.unLock();
        }, "B").start();
    }
}
/**
 * A	 --------come in
 * B	 --------come in
 * A	 --------task over,unLock.........
 * B	 --------task over,unLock.........
 */

7.8 CAS缺点

7.8.1 循环时间长开销很大

在这里插入图片描述

getAndAddInt方法有一个do while
如果CAS失败,会一直进行尝试,如果CAS长时间一直不成功,可能会给CPU带来很大开销

7.8.2 引出来ABA问题?

  • 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 int id;
    private String bookName;
}

public class AtomicStampedReferenceDemo {
    public static void main(String[] args) {
        Book javaBook = new Book(1, "javaBook");
        AtomicStampedReference<Book> atomicStampedReference = new AtomicStampedReference<>(javaBook, 1);
        System.out.println(atomicStampedReference.getReference() + "\t" + atomicStampedReference.getStamp());

        Book mysqlBook = new Book(2, "mysqlBook");
        boolean b;
        b = atomicStampedReference.compareAndSet(javaBook, mysqlBook, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
        System.out.println(b + "\t" + atomicStampedReference.getReference() + "\t" + atomicStampedReference.getStamp());

        b = atomicStampedReference.compareAndSet(mysqlBook, javaBook, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
        System.out.println(b + "\t" + atomicStampedReference.getReference() + "\t" + atomicStampedReference.getStamp());
    }
}
/**
 * Book(id=1, bookName=javaBook)	1
 * true	Book(id=2, bookName=mysqlBook)	2
 * true	Book(id=1, bookName=javaBook)	3
 */

多线程情况下演示AtomicStampedReference解决ABA问题

public class ABADemo {
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {
//        abaHappen();//true	2023
        /**
         * t3	首次版本号: 1
         * t4	首次版本号: 1
         * t3	2次版本号: 2
         * t3	3次版本号: 3
         * false	100	3
         */
        abaNoHappen();

    }

    private static void abaNoHappen() {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号: " + stamp);
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t" + "2次版本号: " + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t" + "3次版本号: " + atomicStampedReference.getStamp());
        }, "t3").start();


        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号: " + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = atomicStampedReference.compareAndSet(100, 200, stamp, stamp + 1);
            System.out.println(b + "\t" + atomicStampedReference.getReference() + "\t" + atomicStampedReference.getStamp());
        }, "t4").start();
    }

    private static void abaHappen() {
        new Thread(() -> {
            atomicInteger.compareAndSet(100, 101);
            try {
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicInteger.compareAndSet(101, 100);
        }, "t1").start();


        new Thread(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicInteger.compareAndSet(100, 2023) + "\t" + atomicInteger.get());//true	2023
        }, "t2").start();
    }
}
  • 一句话:比较加版本号一起上

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

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

相关文章

K8S管理系统项目实战[API开发]-1

前端: Vueelement plus 后端: gogin kubernetes v1.24.2 golang v1.18.3 后端代码地址GitHub - yunixiangfeng/k8s-platform: K8s管理系统后端: gogin Go 快速入门 Gin Web框架 K8s管理系统项目实战[API开发] 项目背景&#xff0c;整体设计&#xff0c;Client-go&#x…

代码随想录算法训练营day34 | 1005.K次取反后最大化的数组和 ,134. 加油站,135. 分发糖果

代码随想录算法训练营day34 | 1005.K次取反后最大化的数组和 &#xff0c;134. 加油站&#xff0c;135. 分发糖果 1005.K次取反后最大化的数组和解法一&#xff1a;两次正常排序解法二&#xff1a;一次排序 134. 加油站135. 分发糖果 1005.K次取反后最大化的数组和 教程视频&a…

生态系统服务(InVEST模型应用)

白老师&#xff08;研究员&#xff09;&#xff1a;长期从事生态系统结构-格局-过程-功能-服务的变化与响应关系等研究工作&#xff1b;重点围绕生物多样性、生态系统服务与价值等&#xff0c;构建生物地球化学模型和评价指标体系&#xff0c;为城市、区域和自然保护区的可持续…

sqli-labs通关(二十三)

第二十三关 这一关是get类型 当输入?id1时&#xff0c;出现报错信息&#xff0c;是由单引号闭合的 但是输入?id1--的时候&#xff0c;还是报错&#xff0c;说明我们的注释符可能被过滤处理了 查看码源&#xff0c;确实是把注释符过滤了 那我们还是根据语句构造 ?id1 or 1…

本地使用3台centos7虚拟机搭建K8S集群教程

第一步 准备3台centos7虚拟机 3台虚拟机与主机的网络模式都是桥接的模式&#xff0c;也就是他们都是一台独立的“主机” &#xff08;1&#xff09;kebe-master的配置 虚拟机配置&#xff1a; 网络配置&#xff1a; &#xff08;2&#xff09;kebe-node1的配置 虚拟机配…

(二)用Qt实现登录界面——打造简洁、美观的登录体验

文章目录 一、引言1、什么是Qt框架&#xff0c;其主要特点是什么&#xff1f;2、Qt框架的应用范围&#xff0c;如何在不同的平台上开发应用程序&#xff1f;3、Qt框架的优点和劣势&#xff0c;与其他框架的比较。 二、相关技术1、socket编程 三、Qt框架的使用1、Qt框架的基本概…

数据库与身份认证

目录 1.数据库的基本概念 1.1什么是数据库 1.2常见的数据库及分类 1.3传统型数据库的数据组织结构 2.安装并配置MySQL 2.1了解需要安装哪些MySQL相关的软件 2.2 MySQL在 Windows 环境下的安装 3.MySQL的基本使用 3.1使用 MySQL Workbench 管理数据库 3.1.1连接数据库…

CSS 圆锥渐变+MASK遮罩实现WIFI图标

前言 &#x1f44f;CSS 圆锥渐变MASK遮罩实现WIFI图标&#xff0c;速速来Get吧~ &#x1f947;文末分享源代码。记得点赞关注收藏&#xff01; 1.实现效果 2.实现步骤 定义css变量–bg背景色&#xff0c;–dot弧宽度&#xff0c;–w父元素宽度&#xff0c;–gap&#xff0c…

docker安装elasticsearch

前言 安装es么&#xff0c;也没什么难的&#xff0c;主要网上搜一搜&#xff0c;看看文档&#xff0c;但是走过的坑还是需要记录一下的 主要参考这三份文档&#xff1a; Running the Elastic Stack on Docker docker简易搭建ElasticSearch集群 Running Kibana on Docker …

模型优化-剪枝 (一)非结构化剪枝

环境 python 3.9numpy 1.24.1pytorch 2.0.0+cu117非结构剪枝 特点 1.简单,将满足某些田间的 weight 置为0即可 2.pytorch容易访问所有参数(weight) 3.最精细化 类型 1.细粒度剪枝(fine-grained) …

数据库创建与管理

目录 一、创建数据库 1&#xff0e;准备创建数据库 2&#xff0e;创建数据库实例分析 方法一&#xff1a;使用对象资源或企业管理器创建数据库 方法二&#xff1a;使用Transact-SQL命令创建数据库 二、管理数据库 1&#xff0e;修改数据库 使用SQL命令修改数据库 2&…

珞珈一号夜间灯光数据处理流程

一、前言 “珞珈一号”卫星是长光卫星技术有限公司首次通过商业化订单为武汉大学研制的卫星,是全球首颗专业夜光遥感卫星,主要用于夜光遥感及导航增强技术验证。卫星整星重量仅为20kg,携带的大视场高灵敏夜光遥感相机,具备130m分辨率、260km幅宽的夜光成像能力,为基于夜光…

【全国大学生统计建模大赛】2023年统计建模大赛赛题解读

目录 关于中国式现代化统计测度的几点思考 &#xff08;一&#xff09;房祥忠教授——大局层面 一、现代化释义 二、现代化度量的单变量方法 2.1 单一变量情形 2.2 分组评分 三、现代化度量的多变量方法 3.1 问题 3.2 解决方法 四、现代化度量的理想化方法 4.1 问题 …

SSM整合详细教学(中)

SSM整合详细教学&#xff08;中&#xff09; 三、异常处理器问题导入1 异常介绍2 异常处理器2.2.1 编写异常处理器2.2.2 RestControllerAdvice注解介绍2.2.3 ExceptionHandler注解介绍 四、项目异常处理方案问题导入1 项目异常分类2 项目异常处理方案3 项目异常处理代码实现3.1…

zookeeper下载安装集群部署

文章目录 一、zookeeper官网下载二、JDK环境安装三、zookeeper安装1.zookeeper解压2.zookeeper配置文件介绍 克隆服务器1.网络检查2.集群配置3.启动集群 一、zookeeper官网下载 下载地址&#xff1a;https://archive.apache.org/dist/zookeeper/找到对应的版本下载 二、JDK环…

Python3.10动态修改Windows系统(win10/win11)本地IP地址(静态IP)

一般情况下&#xff0c;局域网里的终端比如本地服务器设置静态IP的好处是可以有效减少网络连接时间&#xff0c;原因是过程中省略了每次联网后从DHCP服务器获取IP地址的流程&#xff0c;缺点是容易引发IP地址的冲突&#xff0c;当然&#xff0c;还有操作层面的繁琐&#xff0c;…

SpringBoot ( 一 ) 搭建项目环境

1.搭建环境 1.1.创建项目向导 使用idea中的向导创建SpringBoot项目 1.1.1.建立新的项目 位置 : 菜单 > File > New > Project… 1.1.2.选择向导 默认的向导URL 是 https://start.spring.io 建议使用 https://start.aliyun.com 1.1.3.配置项目信息 Group : 组织…

从地方旅游的宣传谈起:如何吸引主流媒体报道,强势刷屏?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 五一小长假已经过去好几天了&#xff0c;大家渐渐的从假期舒适的闲暇转到正常的工作生活中了&#xff0c;但利用五一热点宣传的余温还在继续&#xff0c;今天胡老师就i注意到一篇题为&am…

机器学习之朴素贝斯二、情感分析

目录标题 一、什么是朴素贝叶斯&#xff1f;二、利用朴素贝叶斯进行情感分析1. 数据类别说明2. 什么是词袋模型3. 数据展示4. 利用词袋模型进行词表构建5. 到了这一步&#xff0c;我们的前期工作都已经准备好了&#xff0c;有了样本的向量化数据&#xff0c;开始进行 朴素贝叶斯…

电容笔哪个厂家的产品比较好?苹果平板的电容笔推荐

从目前来说&#xff0c;这个苹果的正版电容笔&#xff0c;售价真的是太贵了&#xff0c;一支就要接近上千元。事实上&#xff0c;对于那些没有很多预算的人来说&#xff0c;平替电容笔是一个很好的选择。一支苹果电容笔&#xff0c;价格是四支平替电容笔的四倍&#xff0c;但平…