并发编程系列之原子操作实现原理

2023-11-15

前言

上节我们讲了并发编程中最基本的两个元素的底层实现,同样并发编程中还有一个很重要的元素,就是原子操作,原子本意是不可以再被分割的最小粒子,原子操作就是指不可中断的一个或者一系列操作。那么今天我们就来看看在多处理器环境下Java是如何保证原子操作的,ok,开始我们今天的并发编程之旅吧。

 

处理器如何实现原子操作

处理器自身会自动保证基本的内存操作原子性,保证从系统内存中读取或者写入一个字节动作是原子性的,也就是说,当处理器读取一个字节时,其他处理器不能访问该字节的内存地址。但是处理器自身只能保证基本的内存操作原子性,对于复杂的操作例如跨总线、跨多个缓存行和跨页表的访问,处理器自身是无法保证原子操作的,只能通过下面两种机制来保证操作原子性:

 

使用总线锁保证原子性:假设多个处理器同时处理一个共享变量,如果没有锁机制,就会出现同一个共享变量同一时刻被多个处理器同时处理的情况,就会造成结果的不一致性,例如i=1,我们要进行2次i++,就会出现i=2和i=3两种结果情况。很明显这不是我们要的结果,所以要想保证读写共享变量的操作是原子性的,就必须保证当处理器1在操作共享变量时,处理器2不能操作缓存了该共享变量内存地址的缓存。

因此引出了总线锁,总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号,其他处理器的请求将被阻塞,那么该处理器就能独占共享内存;

可以想象为:公司有一个会议室(共享内存),各个部门(处理器)开会都在会议室进行,当有一个部门占用会议室时,就会在会议室门口或者公司群里通知会议室此时被占用,那么其他部门的会议就得先等着(阻塞),等该部门结束会议才能开始下一个会议。

 

总线锁的缺点:总线锁把CPU和内存之间的通信锁住了,在锁期间,其他处理器不能操作其他内存地址的数据,开销比较大。

  • 使用缓存锁保证原子性:缓存锁指的是内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上通知LOCK信号,而是修改内部的内存地址,然后通过它的缓存一致性机制来保证操作的原子性;

    缓存一致性机制:缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写被已经锁定的缓存行数据时,会使缓存行失效。

  • 两种情况下不使用缓存锁

    • 当操作的数据不能被缓存在处理器内部,或者操作的数据跨多个缓存行,此时直接用总线锁

    • 当处理器不支持缓存锁定时,即使锁定的内存区域在处理器缓存行中,也会直接使用总线锁

 

Java中如何实现原子操作

Java中主要通过下面两种方式来实现原子操作:锁和循环CAS

锁机制实现原子操作

锁机制保证了只有获得锁的线程才能操作锁定的内存区域,JVM内部实现了很多种锁,偏向锁、轻量级锁和互斥锁,等等,这里就先不对锁进行详细介绍了,偏向锁和轻量级锁,上文也有提到过,感兴趣的可以通过文章末尾点击查阅,这些锁中,除了偏向锁,其他锁的方式都使用了循环CAS,那么我们来看下CAS相关内容。

 

什么是CAS操作

CAS全称Compare-and-Swap(比较并交换),JVM中的CAS操作是依赖处理器提供的cmpxchg指令完成的,CAS指令中有3个操作数,分别是内存位置V、旧的预期值A和新值B。

 

CAS指令如何保证原子性

当CAS指令执行时,当且仅当内存位置V符合旧预期值时A时,处理器才会用新值B去更新V的值,否则就不执行更新,但是无论是否更新V,都会返回V的旧值,该操作过程就是一个原子操作,JDK1.5之后才可以使用CAS,由sun.misc.Unsafe类里面的compareAndSwapInt()和compareAndSwapLong()等方法包装实现,虚拟机在即时编译时,对这些方法做了特殊处理,会编译出一条相关的处理器CAS指令;

但是由Unsafe方法是虚拟机自己调用的,不能直接供用户程序调用,我们只能通过J.U.C包下的类来间接调用,如AtomicInteger和AtomicLong类,这些类中的方法都以原子的方式来进行操作,例如AtomicInteger的incrementAndGet方法中就是使用了compareAndSet,compareAndSet和getAndIncrement等方法都是使用Unsafe的CAS操作;

public final int incrementAndGet() {
       for (;;) {
           int current = get();
           int next = current + 1;
           if (compareAndSet(current, next))
               return next;
       }
   }

先获取到当前的 value 属性值,然后将 value 加 1,赋值给一个局部的 next 变量,然而,这两步都是非线程安全的,但是内部有一个死循环(循环CAS或者称自旋CAS),不断去做compareAndSet操作,直到成功为止,也就是修改的根本在compareAndSet方法里面

public final boolean compareAndSet(int expect, int update) {
       return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
   }

compareAndSwapInt上面提到了是Unsafe类的方法,是基于CAS指令的,其实compareAndSwapInt方法的声明是一个native本地方法

publicfinal native boolean compareAndSwapInt(Object var1, long var2, int var4, intvar5);

 

CAS实现操作的三大问题

CAS虽然很好的解决了原子操作问题,但是仍然存在下面3种问题

ABA问题:使用CAS时因为会先去检查内存位置的旧值A有没有发生变化,发生变化则更新最新值B,但是存在一种情况就是,初次读取内存旧值时是A,再次检查之前这段期间,如果内存位置的值发生过从A变成B再变回A的过程,我们就会错误的检查到旧值还是A,认为没有发生变化,其实已经发生过A-B-A得变化,这就是CAS操作的ABA问题;

解决ABA问题的思路:使用版本号,即1A-2B-3A,这样就会发现1A到3A的变化,不存在ABA变化无感知问题,JDK的atomic包中提供一个带有标记的原子引用类AtomicStampedReference来解决ABA问题,它可以通过控制变量值得版本号来保证CAS的正确性。该类的compareAndSet方法会首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果都相等则以原子方式该引用和该标志的值进行更新。

循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销;

只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,可以使用循环CAS来保证原子操作,但是多个共享变量操作时,就无法保证了。

解决方法

  • 使用锁

  • 将多个变量组合成一个共享变量,jdk提供了AtomicReference类来保证引用对象之间的原子性,那么就可以把多个变量放在一个对象里来进行CAS操作

 

结合上节内容,我们就将并发底层最重要的三个元素实现原理讲了一遍,这里面或多或少有些概念或者操作,大家看得有些模糊,不用担心,后续我们还会继续探讨

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

并发编程系列之原子操作实现原理 的相关文章

  • Java并发编程实战——并发容器之ThreadLocal及其内存泄漏问题

    文章目录 ThreadLocal的简介 ThreadLocal的实现原理 ThreadLocalMap详解 ThreadLocal内存泄漏问题 ThreadLocal的使用场景 ThreadLocal的简介 之前写过用ThreadLocal
  • 并发编程集合

    转载自郑金维老师 一 synchronized 一 原子性 有序性 可见性 1 1 原子性 数据库的事务 ACID A 原子性 事务是一个最小的执行的单位 一次事务的多次操作要么都成功 要么都失败 并发编程的原子性 一个或多个指令在CPU执
  • Java 线程池ExecutorService 等待队列问题

    本人博客原地址 Java 线程池ExecutorService 等待队列问题 创作时间 2019 09 30 11 12 35 1 首先看下Executor获取线程池 这样方式 可以设置线程池的大小 但是了解线程池的内部原理的情况下 这样的
  • Java中的Semaphore信号量机制

    目录 什么是信号量机制 Semaphore工作流程 Semaphore使用方式 什么是信号量机制 信号量机制是一种通过使用计数器来控制共享资源访问的机制 计数器计数的是共享资源的访问许可 如果计数器大于0则允许访问 如果为0 则拒绝访问 J
  • 源码分析【ReentrantLock】原理

    ReentrackLock底层原理 ReentrackLock介绍 非公平锁VS公平 非公平锁 公平锁 可打断VS不可打断 不可打断 默认 可打断模式 锁超时 条件变量 如何在synchronized和ReentrantLock之间进行选择
  • 并发编程JMM系列之重排序和顺序一致性

    前言 昨天我们接触到了什么是Java内存模型以及两种Java并发模型 并对JMM有了一些初步的认识和了解 我们在上节有提到JMM的重排序规则 但是讲的不详细 今天我们再重点聊下重排序这个东西 以及顺序一致性内存模型 OK 开始我们今天的并发
  • 单例模式的4种写法

    单例模式是开发过程中常用的模式之一 首先了解下单例模式的四大原则 构造方法私有 以静态方法或枚举返回实例 确保实例只有一个 尤其是多线程环境 确保反射或反序列化时不会重新构建对象 饿汉模式 饿汉模式在类被初始化时就创建对象 以空间换时间 故
  • Java并发编程:Copy-On-Write机制详解

    前言 在多线程并发访问共享数据时 可能会出现并发问题导致程序崩溃 数据异常等情况 为了避免这些问题 Java中提供了多种并发控制方法 其中Copy On Write COW 机制就是一种常用的技术 本文将详细介绍COW机制的概念 如何保证线
  • 并发编程系列——6线程池核心原理分析

    学习目标 线程池的作用 jdk给我们提供了哪几种常用线程池 线程池有哪几大核心参数 线程池的拒绝策略有哪些 线程中阻塞队列的作用 线程池的工作流程 线程池的设计思维 线程池中的阻塞队列如果用默认的 会有哪些问题 线程池的工作状态有哪些 线程
  • 多线程-Thread类的常用方法及使用场景

    众所周知 操作线程就必须熟读线程的API方法 万一你开个多线程刹不住车就歇菜了 下面就介绍一些API基本用法 包括sleep join yield interrupt sleep 让当前线程睡一会 原生用法Thread sleep 毫秒 会
  • 什么是ConcurrentHashMap?

    文章目录 什么是ConcurrentHashMap ConcurrentHashMap 的主要特性 ConcurrentHashMap 的用法 ConcurrentHashMap 的实现原理 在什么场景使用ConcurrentHashMap
  • 生产者与消费者问题?

    生产者消费者模式是并发 多线程编程中经典的设计模式 简单来看 就是一个类负责生产 一个类负责消费 举例来说 一个变量 生产者不断增加这个变量 消费者不断减少这个变量 在互联网应用中 抢票机制就是应用了该模式 比如大麦网演唱会门票抢票 123
  • C++ std::thread多线程详解

    c 多线程详解 一 std thread线程创建 1 函数指针 2 Lambda函数 3 functor Funciton Object 4 非静态成员函数 5 静态成员函数 二 std thread线程停止 1 join函数 2 deta
  • shell编程笔记3--shell并发

    shell编程笔记3 shell并发 shell编程笔记3 shell并发 介绍 并发方法 1 简单后台方式 2 普通控制并发量方式 3 通过管道控制并发量 参考文献 shell编程笔记3 shell并发 介绍 在shell中适当使用并发功
  • 线程的状态与切换

    Java中的线程的生命周期大体可分为5种状态 1 新建 初始化 NEW 新创建了一个线程对象 2 可运行 RUNNABLE 线程对象创建后 其他线程 比如main线程 调用了该对象的start 方法 该状态的线程位于可运行线程池中 等待被线
  • Lock锁

    Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作 它们允许更灵活的结构化 可能具有完全不同的属性 并且可以支持多个相关联的对象Condition 1 传统的synchronized package cn d
  • 深入理解synchronized底层原理,一篇文章就够了!

    文章目录 前言 一 synchronized的特性 1 1 原子性 1 2 可见性 1 3 有序性 1 4 可重入性 二 synchronized的用法 三 synchronized锁的实现 3 1 同步方法 3 2 同步代码块 四 syn
  • 并发编程4 - 线程状态、死锁及ReentrantLock

    文章目录 一 再述线程状态转换 二 多把锁与线程活跃性问题 1 多把锁 2 活跃性 三 ReEntrantLock 1 基本用法 2 可重入 3 可打断 4 锁超时 5 公平锁 6 条件变量 一 再述线程状态转换 情况1 New RUNNA
  • 并发编程 (6)一不小心就死锁了,怎么办?

    在上一篇文章中 我们用 Account class 作为互斥锁 来解决银行业务里面的转账问题 虽然这个方案不存在并发问题 但是所有账户的转账操作都是串行的 例如账户 A 转账户 B 账户 C 转账户 D 这两个转账操作现实世界里是可以并行的
  • Java线程(Thread)生命周期的6种状态

    当线程被创建并启动以后 它既不是一启动就进入了执行状态 也不是一直处于执行状态 在线程的生命周期中 可能处于不同的状态 java lang Thread State 列举出了这6种线程状态 线程状态 导致状态发生条件 New 新建 线程刚被

随机推荐

  • 【Android】APT与JavaPoet学习与实战

    PS 本文讲解的APT全称为Annotation Processing Tool 而非是Android Performance Tuner 这两种工具简称皆为APT 前者是 注释处理工具 后者是 Android性能调试器 本文分别使用Jav
  • 使用--link实现容器互联,很简单

    大家好 今天分享docker 使用 link实现容器互联 运行镜像 root localhost docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES root lo
  • 基于STM32MP157调试MIPI-DSI屏幕

    平台 STM32MP157 屏幕 mipi dsi接口 1024x600 内核版本 linux5 4 本人是第一次调试mipi屏 在157这个平台上遇到的问题有一点多 接下来简单的描述下我的调试经验 一 先配置一下设备树DTB ltdc p
  • 学习java随堂练习-20220617

    目录 第1题 今天是学习Java的第十四天 1道练习题 第1题 题目 运行结果 代码如下 1 Student 描述学生类 属性 学号 姓名 性别 电话 方法 显示详细信息 public class Student private Strin
  • java代码 阿里云短信 手机接受验证码

    package com antifreeze server controller import com aliyuncs DefaultAcsClient import com aliyuncs IAcsClient import com
  • AppDomain 和动态加载

    应用程序体系结构 在我专攻代码之前 我想谈谈我尝试做的事 您可能记得 SuperGraph 让您从函数列表中进行选择 我希望能够在具体的目录中放置外接程序程序集 让 SuperGraph 检测它们 加载它们 并找到它们中包含的所有函数 如果
  • DCDC电源设计中需要考虑的问题

    一 电子开关设计 1 为什么用MOS管做开关管 2 MOS驱动电路用图腾柱还是用推挽电路 3 MOS悬浮电压设计思想以及工作原理 二 PWM驱动波形 1 频率如何设置 2 占空比如何调整 3 三角波生成电路如何设计 4 比较器参考电压如何选
  • linux下的主要目录

    2019独角兽企业重金招聘Python工程师标准 gt gt gt Linux系统目录结构 登录系统后 在当前命令窗口下输入 ls 你会看到 以下是对这些目录的解释 bin bin是Binary的缩写 这个目录存放着最经常使用的命令 boo
  • 将lgbm模型进行5折交叉验证,并用GridSearchCV进行超参搜索,并打印输出每一折的精度...

    你可以使用 sklearn 的 GridSearchCV 函数来实现 lgbm 模型的 5 折交叉验证和超参数搜索 首先 需要定义模型和要调整的超参数的范围 import lightgbm as lgb from sklearn model
  • 1001 害死人不偿命的(3n+1)猜想 (15分)_Quentin

    题目链接 1001 害死人不偿命的 3n 1 猜想 15分 卡拉兹 Callatz 猜想 对任何一个正整数 n 如果它是偶数 那么把它砍掉一半 如果它是奇数 那么把 3n 1 砍掉一半 这样一直反复砍下去 最后一定在某一步得到 n 1 卡拉
  • 【翻译】知识的诅咒

    巧合的是 本周我和五组不同的人进行了同样的对话 我想我应该把我的想法写下来 并把它们写在博客上 因为这一连串的想法似乎引起了很多人的共鸣 这场对话从一个偏见开始 和我一起工作的许多人是工程师 他们后来可能已经成为高级领导或高管 但他们曾经是
  • ios 固定定位 input获取焦点,ios 滚动条滚动 fixed固定定位失效,位置偏移

    http efe baidu com blog mobile fixed layout 还发现一个问题就是ios input设置readonly 还是能看到光标 然后解决方法是在行内写了 nf cus this blur
  • Zabbix 4.0升级5.0 &&ES 6.1升级7.0

    Zabbix 4 0升级5 0 一 升级方案 1影响范围 升级期间 不会影响到现有的系统 系统将保持正常的运行 升级完成后 将进行一段时间的可用性测试 待系统稳定后将替换生产上的监控 2升级方法 本次升级采用蓝绿部署的方式 先在测试环境重新
  • com中abc.h不能修改,只能修改abc.idl,生成abc.h

    如题 犯了这个错误
  • 小笔记1:在Unity中导入模型后,材质被锁定后无法更改

    每天进步一点点小笔记 解决方案 方法1 在资源里查找到该模型 右侧inspector栏 Materials location选择Use External Material 点击Apply导入便可以编辑 方法2 在资源里查找到该模型 右侧in
  • opkg软件源设置

    opkg软件源定义在 etc opkg distfeeds conf 更新 etc opkg conf并没有什么卵用 文件中 包含软件源索引的目录路径 分为base luci management packages routeing tel
  • live555 流媒体开源库

    live555对每一个从事过流媒体开发的从业者而言 都不曾陌生 就像每一个从事音视频行业的从业者而言 ffmpeg也不曾陌生 随着行业需求的发展 live555也是越见强大 因前几天帮朋友项目查找问题 重拾live555 没想到时隔10年
  • 树莓派修改国内软件源

    编辑sources list文件 sudo nano etc apt sources list 注释掉现有的代码 新增以下代码 deb http mirrors tuna tsinghua edu cn raspbian raspbian
  • 精准营销获客如何成为企业未来的发展趋势 ,运营商大数据

    精准营销最大的优势在于 精准 即在细分市场的基础上 对不同的消费者进行详细分析 确定目标受众 精准营销的主要特点如下 1 数据范围广 可以说是全球数据 目前 中国三大运营商覆盖了数十亿互联网用户 可以说是非常全面的 可以满足各个行业的需求
  • 并发编程系列之原子操作实现原理

    前言 上节我们讲了并发编程中最基本的两个元素的底层实现 同样并发编程中还有一个很重要的元素 就是原子操作 原子本意是不可以再被分割的最小粒子 原子操作就是指不可中断的一个或者一系列操作 那么今天我们就来看看在多处理器环境下Java是如何保证