Java原子类操作原理剖析
◆
CAS的概念
◆
对于并发控制来说,使用锁是一种悲观的策略。它总是假设每次请求都会产生冲突,如果多个线程请求同一个资源,则使用锁宁可牺牲性能也要保证线程安全。而无锁则是比较乐观的看待这个问题,它会假设每次访问都没有冲突,这样就提高了效率。但是事实难料、这个冲突是避免不了的,无锁也考虑到了肯定会遇到冲突,对于冲突的解决无锁就使用一种比较交换(CAS)的技术来检测冲突。一旦检测到冲突就重试当前操作直到成功为止。
◆
CAS算法
◆
CAS机制中使用了3个基本操作数CAS(V,E,N):V表示要更新的变量,E表示预期值,N表示新值。
CAS更新一个变量的时候,只有当变量的预期值E和要更新的变量V的实际值相同时,才会将V的值修改为N。
一个简单的例子:
在内存地址V当中,存储一个值为1的变量。
此时线程1想把变量的值增加1.对线程1来说,预期值E=1,要修改的新值N=2.
在线程1要提交更新之前,另一个线程2抢先一步,把V的值率先更新成了2。
此时线程1开始提交更新,首先进行预期值E和变量V的实际值比较,发现E不等于V的实际值,提交失败。
失败后线程1 重新获取内存地址V的当前值,并重新计算想要修改的值。此时对线程1来说,E=2,V=2。这个重新尝试的过程被称为自旋。
如果这一次依然在提交时发现被线程2把V值更新到了3则再次重复步骤5。此时E=3,V=3
步骤5执行执行完毕后再次更新发现没有其他线程改变V的值。线程1进行比较,发现A和V的值是相等的。则线程1进行交换,把V的值替换为N,也就是2.
◆
Java中CAS的底层实现
◆
我们看一下AtomicInteger当中常用的自增方法incrementAndGet:
1 |
public final int incrementAndGet() { |
这里涉及到两个重要的对象,一个是unsafe,一个是valueOffset。
unsafe是什么东西呢?它JVM为我们提供了一个访问操作系统的后门,unsafe为我们提供了硬件级别的原子操作。而valueOffset对象,是通过unsafe.objectFiledOffset方法得到,所代表的是AtomicInteger对象value成员变量在内存中的偏移量。我们可以简单的把valueOffset理解为value变量的内存地址。
而unsafe的getAndAddInt方法顾名思义就是使用操作系统的原子操作来为我们实现当前的的++操作并把旧值返回回来。因为是返回的旧值所以
incrementAndGet方法返回的数据应该是这个旧值加上1
◆
CAS的缺点
◆
CPU开销过大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。
不能保证代码块的原子性
CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。
ABA问题
这是CAS机制最大的问题所在。
我们现在来说什么是ABA问题。
假设小王账户有1000块钱,即v=1000。
这时有三个线程想使用CAS的方式更新这个小王的账户。线程1和线程2已经获取当前账户余额为1000,线程3还未获取当前值。
线程1为花呗扣款、线程2为花呗扣款的备用操作(避免第一次扣款失败),线程3为工资入账
接下来,线程1先一步执行成功,把当前账户成功从1000减少到500;同时线程2因为某种原因被阻塞住,没有及时扣款;线程3在线程1扣款之后,获取了当前值500。
在之后,线程2仍然处于阻塞状态,线程3继续执行,成功入账工资500,把当前值又变回了1000。
此时,线程2恢复运行状态,进行更新之前查询E和V相同,所以毫不犹豫的进行又一次账户扣款。
这种扣款的方式对于小王来说肯定是不可接受的(估计都要疯了),解决方案就是在操作的时候加个版本号或者是时间戳来标示状态信息。
同样以刚才的例子来说:
假设小王账户有1000块钱,即v=1000。
这时有三个线程想使用CAS的方式更新这个小王的账户。线程1和线程2已经获取当前账户余额为1000,线程3还未获取当前值。但是呢,这里线程1和2还需要记录一个获取当前账户余额的最后更新时间,比如9.30.
同样的线程1为花呗扣款、线程2为花呗扣款的备用操作(避免第一次扣款失败),线程3为工资入账。
接下来,线程1先一步执行成功,把当前账户成功从1000减少到500;此时账户余额的时间戳就已经变了,比如9.31。同时线程2因为某种原因被阻塞住,没有及时扣款;线程3在线程1扣款之后,获取了当前值500和时间戳9.31。
在之后,线程2仍然处于阻塞状态,线程3继续执行,成功入账工资500,把账户又变回了1000,同时时间戳更新为9.32。
此时,线程2恢复运行状态,进行更新之前查询E和V虽然相同,但是时间戳确是不一样的。
◆
Java提供的12种原子操作类
◆
原子更新基本类型
1 |
AtomicBoolean:原子更新布尔类型。 |
原子更新数组
1 |
AtomicIntegerArray:原子更新整型数组里的元素。 |
原子更新引用类型
1 |
AtomicReference:原子更新引用类型。 |
原子更新字段
1 |
AtomicIntegerFieldUpdater:原子更新整型字段的更新器。 |
Java原子类操作原理剖析的更多相关文章
- 对Java原子类AtomicInteger实现原理的一点总结
java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...
- Java原子类中CAS的底层实现
Java原子类中CAS的底层实现 从Java到c++到汇编, 深入讲解cas的底层原理. 介绍原理前, 先来一个Demo 以AtomicBoolean类为例.先来一个调用cas的demo. 主线程在f ...
- Java原子类AtomicInteger实现原理的一点总结
java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...
- 死磕 java原子类之终结篇(面试题)
概览 原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换. 原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割 ...
- 源码编译OpenJdk 8,Netbeans调试Java原子类在JVM中的实现(Ubuntu 16.04)
一.前言 前一阵子比较好奇,想看到底层(虚拟机.汇编)怎么实现的java 并发那块. volatile是在汇编里加了lock前缀,因为volatile可以通过查看JIT编译器的汇编代码来看. 但是原子 ...
- Java 原子类 java.util.concurrent.atomic
Java 原子类 java.util.concurrent.atomic 1.i++为什么是非线程安全的 i++其实是分为3个步骤:获取i的值, 把i+1, 把i+1的结果赋给i 如果多线程执行i++ ...
- Java原子类实现原理分析
在谈谈java中的volatile一文中,我们提到过并发包中的原子类可以解决类似num++这样的复合类操作的原子性问题,相比锁机制,使用原子类更精巧轻量,性能开销更小,本章就一起来分析下原子类的实现机 ...
- Java原子类及内部原理
一.引入 原子是世界上的最小单位,具有不可分割性.比如 a=0:(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作是原子操作.再比如:a++: 这个操作实际是a = a + ...
- Java原子类--AtomicLongArray
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3514604.html AtomicLongArray介绍和函数列表 在"Java多线程系列-- ...
随机推荐
- GMT与Etc/GMT地区信息的时区转换
GMT 地区信息的时区 在将来的版本中可能不再支持以下左面一列中的地区信息的时区.可能从 /usr/share/lib/zoneinfo 删除这些文件.左列中的地区信息的时区用右列中对等的时区来替换. ...
- codeforces 985C Liebig's Barrels
题意: 有n * k块木板,每个木桶由k木板组成,每个木桶的容量定义为它最短的那块木板的长度. 任意两个木桶的容量v1,v2,满足|v1-v2| <= d. 问n个木桶容量的最大的和为多少,或者 ...
- centos系统查看系统版本、内核版本、系统位数、cpu个数、核心数、线程数
centos查看系统版本 cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 1)查看centos内核的版本: [root@loc ...
- Python3的requests类抓取中文页面出现乱码的解决办法
这种乱码现象基本上都是编码造成的,我们要转到我们想要的编码,先po一个知识点,嵩天老师在Python网络爬虫与信息提取说到过的:response.encoding是指从HTTP的header中猜测 ...
- Java7里try-with-resources分析
这个所谓的try-with-resources,是个语法糖.实际上就是自动调用资源的close()函数.和Python里的with语句差不多. 例如: [java] view plain copy ...
- bzoj5252 [2018多省省队联测]林克卡特树
斜率优化树形dp?? 我们先将问题转化成在树上选K+1条互不相交路径,使其权值和最大. 然后我们考虑60分的dp,直接维护每个点子树内选了几条路径,然后该点和0/1/2条路径相连 然后我们会发现最后的 ...
- java技术树+必读书籍
引子 本篇文章用技术树的形式来展示java相关技术栈.所有技术点有博客的都自带链接,没有的后续加上. 必读书籍推荐: 1.java基础: <effective java>-->四星推 ...
- 在openjdk8下看Unsafe源码
尽管有传言JAVA9可能会移除Unsafe类,但不妨碍我们理解它的原理.因为类不在了,native方法还在那里.就像菜换样了,食材就那些没变.一个好厨师不仅仅需要会做菜,还需要能识别食材特性...来吧 ...
- React 中阻止事件冒泡的问题
在正式开始前,先来看看 JS 中事件的触发与事件处理器的执行. JS 中事件的监听与处理 事件捕获与冒泡 DOM 事件会先后经历 捕获 与 冒泡 两个阶段.捕获即事件沿着 DOM 树由上往下传递,到达 ...
- TCP报文解析
概述 在<网络基础总结(一)>总结了TCP建立连接和断开连接的流程,然而TCP协议远比我所了解的复杂得多,我所知的可以说就冰山一角,所总结的也只是纸上谈兵,仅仅只能对TCP有个肤浅的认识, ...