atomic 包底层实现原理
一、概念介绍
(一)volatile关键字
Java 因为指令重排序,优化我们的代码,让程序运行更快,也随之带来了多线程下,指令执行顺序的不可控。
1.volatile关键字的作用:
内存可见性,修饰的变量发生改变之后对所有线程立即可见
禁止指令重排序
volatile的底层是通过内存屏障实现的,第一个作用是禁止指令重排。内存屏障另一个作用是强制更新一次不同 CPU 的缓存。
synchronized 看作重量级的锁,而 volatile 看作轻量级的锁 。synchronized使用的锁的层面是在JVM层面,虚拟机处理字节码文件实现相关指令。volatile 底层使用多核处理器实现的 lock 指令,更底层,消耗代价更小。
(二)CAS
CAS 的全称是 Compare-And-Swap , 它是一条 CPU 并发原语。
CAS 并不是一种实际的锁,它仅仅是实现乐观锁的一种思想,java 中的乐观锁(如自旋锁)基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
乐观锁一般会使用版本号机制或 CAS 算法实现
1.版本号机制
一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数,当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。
2.CAS 算法
即 compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。 CAS 算法涉及到三个操作数
需要读写的内存值 V
进行比较的值 A
拟写入的新值 B
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
3.synchronized与CAS的比较
synchronized涉及线程之间的切换,存在用户状态和内核状态的切换,耗费巨大。CAS只是CPU的一条原语,是一个原子操作,消耗较少。
二、atomic 的实现原理
Atomic 包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。
Atomic 系列的类中的核心方法都会调用 unsafe 类中的几个本地方法。这个类包含了大量的对 C 代码的操作,包括很多直接内存分配以及原子操作的调用,而它之所以标记为非安全的,是告诉你这个里面大量的方法调用都会存在安全隐患。unsafe 是 java 提供的获得对对象内存地址访问的类,它的作用就是在更新操作时提供 “比较并替换” 的作用。
CAS 并发原语现在 Java 语言中就是 sun.misc.Unsafe 类的各个方法,调用 Unsafe 类中的 CAS 方法,JVM 会帮我们实现 CAS 汇编指令,这是一种完全依赖硬件的功能,通过它实现了原子操作,由于 CAS 是一种系统原语,原语属于操作系统用语范畴,是由于诺干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说 CAS 是一条 CPU 原子指令,不会造成所谓的数据不一致问题。
可以看出atomic证原子性就是通过:自旋 + CAS(乐观锁)
仔细分析 concurrent 包的源代码实现,会发现一个通用化的实现模式:
首先,声明共享变量为 volatile;
然后,使用 CAS 的原子条件更新来实现线程之间的同步;
同时,配合以 volatile 的读 / 写和 CAS 所具有的 volatile 读和写的内存语义来实现线程之间的通信。
优缺点:
CAS 相对于其他锁,不会进行内核态操作,有着一些性能的提升。但同时引入自旋,当锁竞争较大的时候,自旋次数会增多。cpu 资源会消耗很高。CAS + 自旋适合使用在低并发有同步数据的应用场景。
三、atomic 的 ABA问题
多个线程即可以入列也可以出列,也就是数据的操作方向不一致,那么可能出现 ABA 的情况。
T1 线程准备出栈,对于出栈操作我们只需要将栈顶位置由 sp 通过 CAS 操作更新为 newSP 即可,如图 1 所示。但是在 T1 线程执行 tail.compareAndSet (sp,newSP) 之前系统进行了线程调度,T2 线程开始执行。T2 执行了三个操作,A 出栈,B 出栈,然后又将 A 入栈。此时系统又开始调度,T1 线程继续执行出栈操作,但是在 T1 线程看来,栈顶元素仍然为 A,(即 T1 仍然认为 B 还是栈顶 A 的下一个元素),而实际上的情况如图 2 所示。T1 会认为栈没有发生变化,所以 tail.compareAndSet (sp,newSP) 执行成功,栈顶指针被指向了 B 节点。而实际上 B 已经不存在于堆栈中,T1 将 A 出栈后的结果如图 3 所示,这显然不是正确的结果。
解决方法:
除了要比较当对象的前值和预期值以外,还要比较当前(操作的)戳值和预期(操作的)戳值,当全部相同时,compareAndSet 方法才能成功。每次更新成功,戳值都会发生变化,戳值的设置是由编程人员自己控制的。
————————————————
版权声明:本文为CSDN博主「温柔的谢世杰」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_33945246/article/details/104020878
atomic 包底层实现原理的更多相关文章
- Java中Atomic包的实现原理及应用
1. 同步问题的提出 假设我们使用一个双核处理器执行A和B两个线程,核1执行A线程,而核2执行B线程,这两个线程现在都要对名为obj的对象的成员变量i进行加1操作,假设i的初始值为0,理论上两个线程运 ...
- 并发包学习之-atomic包
一,模拟并发代码: 线程不安全的代码 //并发模拟代码 public class CountExample { //请求总数 public static int clientTotal = 5000; ...
- synchronized底层实现原理&CAS操作&偏向锁、轻量级锁,重量级锁、自旋锁、自适应自旋锁、锁消除、锁粗化
进入时:monitorenter 每个对象有一个监视器锁(monitor).当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:1 ...
- atomic 包、synchronized | Java 中线程安全
相关阅读 彻底搞懂 CPU 中的内存结构 Java 内存模型 ,一篇就够了! 多线程实现原理 之前已经说过了,我们在保证线程安全的时候主要就是保证线程安全的 3 大特性,原子性.可见性.有序性.而在 ...
- 24.Java中atomic包中的原子操作类总结
1. 原子操作类介绍 在并发编程中很容易出现并发安全的问题,有一个很简单的例子就是多线程更新变量i=1,比如多个线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过Synchr ...
- Java并发—原子类,java.util.concurrent.atomic包(转载)
原子类 Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中 的原子操作类提供了一种用法简单.性能高效.线程安全地更新一个变量 ...
- Java 并发系列之二:java 并发机制的底层实现原理
1. 处理器实现原子操作 2. volatile /** 补充: 主要作用:内存可见性,是变量在多个线程中可见,修饰变量,解决一写多读的问题. 轻量级的synchronized,不会造成阻塞.性能比s ...
- Java并发编程系列-(8) JMM和底层实现原理
8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...
- java中Atomic变量的实现原理是怎样的?
转载自: Java3y https://www.zhihu.com/question/39130725/answer/1006948362 一.基础铺垫 首先我们来个例子: public class ...
- Java中的Atomic包使用指南
Atomic包介绍 在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段.Atomic包里的类基本都是使用Unsafe实现的包装类. 原 ...
随机推荐
- Andrew 算法求凸包
Andrew 算法求凸包 参考资料: 右手定则(baidu.com) 内积和外积 - OI Wiki (oi-wiki.org) \(a\) 与 \(b\) 相对位置 \(b\) 在 \(a\) 的逆 ...
- 适合才最美:Shiro安全框架使用心得
大家好,我是 V 哥.Apache Shiro 是一个强大且灵活的 Java 安全框架,专注于提供认证.授权.会话管理和加密功能.它常用于保护 Java 应用的访问控制,特别是在 Web 应用中.相比 ...
- 强化学习算法——TPG算法(遗传编程GP算法)代码
tpg算法是一个使用模块涌现和复用机制的遗传编程(GP)算法,该算法在一些强化学习问题上有着不错的表现,本文给出该算法的项目地址. tpg算法的C++实现代码大概有1万的逻辑代码,如果这个比例换做使用 ...
- UIAbility组件生命周期
当用户打开.切换和返回到对应应用时,应用中的UIAbility实例会在其生命周期的不同状态之间转换.UIAbility类提供了一系列回调,通过这些回调可以知道当前UIAbility实例的某个状态发生改 ...
- 系统框架(delphi)
写了一个简单的框架,参考ERP系统写的,可使用两层(client+DB),或三层(client+app<datasnap>+DB)的方式运行,非com+方式. 哈哈,登录好俗...... ...
- 拯救php性能的神器webman-使用后台webman-admin
在webman的插件市场里面发现了这个 webman-admin 安装的话很简单,就是在已经安装了 webman 的目录里面执行 composer require -W webman/admin 安 ...
- java三次大作业的全面总结
一:前言 知识点总结: 数据结构:题目涉及到了字典或哈希表用于存储题目信息.试卷信息和学生信息:列表用于存储多个题目.试卷和学生的集合:对象用于封装题目.试卷.学生和答案的具体属性. 字符串解析:题目 ...
- [昌哥IT课堂]|欢迎 MySQL 9.0,回顾 Oracle 在 8.0 版中的管理(译)
对于新兴技术和社区的管理是相对容易的.经过 29 年发展,MySQL 已成为全球数百万用户中使用最广泛且备受信任的开源数据库之一.在这一规模的社区领导中可能存在复杂性.我们努力寻求稳定和创新的平衡,为 ...
- Docker容器使用问题:Failed to get D-Bus connection: Operation not permitted
原因是dbus-daemon没能启动.其实systemctl并不是不可以使用.将你的CMD或者entrypoint设置为/usr/sbin/init即可.如: docker run --privile ...
- vim 编辑 运行 shell 文件
Vi/Vim 是所有 Unix/Linux 操作系统默认配备的编辑器.因其强大的功能和高效的操作,Vi/Vim 也成为众多 Unix/Linux 用户.管理员必须掌握并熟练使用的编辑工具之一.尤其是在 ...