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
2
3
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

这里涉及到两个重要的对象,一个是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
2
3
AtomicBoolean:原子更新布尔类型。
AtomicInteger:原子更新整型。
AtomicLong:原子更新长整型。

原子更新数组

1
2
3
AtomicIntegerArray:原子更新整型数组里的元素。
AtomicLongArray:原子更新长整型数组里面的元素。
AtomicReferenceArray:原子更新引用类型数组里的元素。

原子更新引用类型

1
2
3
AtomicReference:原子更新引用类型。
AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
AtomicMarkableReference:原子更新带有标记位的引用类型。

原子更新字段

1
2
3
AtomicIntegerFieldUpdater:原子更新整型字段的更新器。
AtomicLongFieldUpdater:原子更新长整型字段的更新器。
AtomicStampedReference:原子更新带有版本号的引用类型。

Java原子类操作原理剖析的更多相关文章

  1. 对Java原子类AtomicInteger实现原理的一点总结

    java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...

  2. Java原子类中CAS的底层实现

    Java原子类中CAS的底层实现 从Java到c++到汇编, 深入讲解cas的底层原理. 介绍原理前, 先来一个Demo 以AtomicBoolean类为例.先来一个调用cas的demo. 主线程在f ...

  3. Java原子类AtomicInteger实现原理的一点总结

    java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...

  4. 死磕 java原子类之终结篇(面试题)

    概览 原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换. 原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割 ...

  5. 源码编译OpenJdk 8,Netbeans调试Java原子类在JVM中的实现(Ubuntu 16.04)

    一.前言 前一阵子比较好奇,想看到底层(虚拟机.汇编)怎么实现的java 并发那块. volatile是在汇编里加了lock前缀,因为volatile可以通过查看JIT编译器的汇编代码来看. 但是原子 ...

  6. Java 原子类 java.util.concurrent.atomic

    Java 原子类 java.util.concurrent.atomic 1.i++为什么是非线程安全的 i++其实是分为3个步骤:获取i的值, 把i+1, 把i+1的结果赋给i 如果多线程执行i++ ...

  7. Java原子类实现原理分析

    在谈谈java中的volatile一文中,我们提到过并发包中的原子类可以解决类似num++这样的复合类操作的原子性问题,相比锁机制,使用原子类更精巧轻量,性能开销更小,本章就一起来分析下原子类的实现机 ...

  8. Java原子类及内部原理

    一.引入 原子是世界上的最小单位,具有不可分割性.比如 a=0:(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作是原子操作.再比如:a++: 这个操作实际是a = a + ...

  9. Java原子类--AtomicLongArray

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3514604.html AtomicLongArray介绍和函数列表 在"Java多线程系列-- ...

随机推荐

  1. YII框架组件CListView实现分页

    效果是普通分页就不截图了... C控制器方法如下: $criteria=new CDbCriteria; $criteria->condition = "`status`={$newS ...

  2. 在Python中用Request库模拟登录(四):哔哩哔哩(有加密,有验证码)

    !已失效! 抓包分析 获取验证码 获取加密公钥 其中hash是变化的,公钥key不变 登录 其中用户名没有被加密,密码被加密. 因为在获取公钥的时候同时返回了一个hash值,推测此hash值与密码加密 ...

  3. HttpClient 专题

    HttpClient is a HTTP/1.1 compliant HTTP agent implementation based on HttpCore. It also provides reu ...

  4. Java描述数据结构之链表的增删改查

    链表是一种常见的基础数据结构,它是一种线性表,但在内存中它并不是顺序存储的,它是以链式进行存储的,每一个节点里存放的是下一个节点的"指针".在Java中的数据分为引用数据类型和基础 ...

  5. 当Ucenter和应用通信失败

    http://blog.sina.com.cn/s/blog_775f158f010135uz.html 失败是常见的. 对于初次接触Ucenter的人来讲,添加一个自己的应用最头疼的就是发现通信失败 ...

  6. 跟我学ASP.NET MVC之三:完整的ASP.NET MVC程序-PartyInvites

    摘要: 在这篇文章中,我将在一个例子中实际地展示MVC. 场景 假设一个朋友决定举办一个新年晚会,她邀请我创建一个用来邀请朋友参加晚会的WEB程序.她提出了四个注意的需求: 一个首页展示这个晚会 一个 ...

  7. 根据NABCD原则完成的案例项目需求分析及其创新方法

    <well> Need:在当下的知识经济时代,社会效率在提升,社会竞争强度也在增大,现代社会对人的素质提出了更高的要求,这导致了越来越多的人心理压力增大.well产品就是为了缓解人们心理压 ...

  8. 【差分+前缀和】BZOJ1637: [Usaco2007 Mar]Balanced Lineup

    Description Farmer John 决定给他的奶牛们照一张合影,他让 N (1 ≤ N ≤ 50,000) 头奶牛站成一条直线,每头牛都有它的坐标(范围: 0..1,000,000,000 ...

  9. BZOJ_2956_模积和_数学

    BZOJ_2956_模积和_数学 Description 求∑∑((n mod i)*(m mod j))其中1<=i<=n,1<=j<=m,i≠j. Input 第一行两个数 ...

  10. jquery中attr()和prop()的区别

    最近项目回归使用jquery,页面渲染全是使用jquery做的,所以做的时候也遇到了许多以前没有见过的问题,如这次操作[radio]控件的"checked"属性时有遇到问题, $( ...