JMM内存模型,JVM分配策略,Volatile+AtomicX
为了深入理解CAS的,我们从以下几个维度去探究CAS,然后再去考虑为什么出现ABA问题。
1、CAS是什么?
2、为什么需要CAS算法?
3、CAS解决的是什么问题?
4、在jdk中有哪些是基于CAS实现的?
5、CAS底层实现原理是什么?
6、如何自己编写一个类似CAS算法的实现【简单版本】
7、CAS有哪些问题?
8、CAS的ABA问题到底是什么意思?如何解决ABA问题?
CAS是什么?
CAS:一个算法,全称为Compare And Swap,比较且交换。在CAS中存在三个值,
V:内存值
A:旧值或者叫预期值
B:更新值
当且仅当A==V (预期值==内存值),才会出现V=B,将更新值赋给内存值,否则什么都不做。
问题:为什么需要CAS算法,到底CAS在做什么?三个值分别是什么意思?
接下来我们通过一个个的测试用例去探究CAS:
测试用例1
在多线程情景下:
多线程情景下修改值结果:
多线程情景下修改值结论:在多线程下,无法通过主线程改变其他线程中的内容。
探究原因:
- java内存模型(JMM:java memory mode)
-
多线程下如何处理共享数据?
Java内存模型
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享。
Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成。
注意:这里的主内存、工作内存与Java内存区域的Java堆、栈、方法区不是同一层次内存划分,这两者基本上没有关系
测试用例2:解决可见性问题
解决方案:通过在共享变量上加入volatile
关键词。volatile
关键词只能保证内存可见性,而不能保证原子性。 【ps,关于volatile我们这次不做探究,当然这里除了这种方式之外还有其他解决办法,我们也暂且先不做深入】
加入volatile测试代码:
volatile测试代码结果:
测试结果测试用例3:测试原子性问题
测试用例3:测试原子性问题结果:
测试结果问题: 通过volatile不能保证我们原来数据的原子性问题,继续往下深入
测试用例4:解决原子性问题
测试用例4:解决原子性问题注意这里的标注区域,通过AtomicInteger保证原子性
结果:
测试结果解决方案:通过使用java.util.concurrent.atomic.AtomicInteger帮助我们解决原子性问题,而截至到这里我们才真正意义上准备接触CAS算法。这里对于AtomicXXX 不多做扩展,我们回来继续了解CAS。
CAS解决了什么问题?
CAS帮助我们解决了数据一致性问题,或者是在上述的例子中,我们通过AtomicXXX类确保数据的原
子性。这里的数据一致性我们已经在多线程情景下去模拟过了。
并且在高并发CAS算法也能帮助我们很好的处理数据一致性问题,且它的实现是一种基于乐观锁的
策略实现。这样要比直接使用Synchronized要来的更加高效,但是这里注意CAS本身的算法
实现我们说是不稳定的由于这个问题也出现了我们说的ABA问题。
在jdk中有哪些是基于CAS实现的?
java.utit.concurrent.*;就是基于CAS算法实现的。没有CAS就没有这个包;
PS:深入扩展一下,比如jvm对于对象分配时也会使用CAS算法。
java堆中的内存分配【这里简单扩展】
我们都知道当执行new操作的时候,此时其实对象在堆中分配大小的空间是已经确定了的,因为我们
知道java是强类型语言。不同数据类型在堆中所占空间时确定的。
那么当空间确定之后jvm到底是如何分配的呢?
内存分配策略:
单线程分配策略
指针碰撞
一般适用于内存绝对规整的[内存是否规整取决于回收策略],分配空间时只是通过指针向空闲内存中移动创建对象大小的位置即可。
空闲列表
一般适用于内存非规整的,此时jvm内部维护了一个列表[内存列表]。这个列表中记录哪些空间时空闲的。分配空间时会在这个列表中查看,找寻合适的区域,然后进行使用。
多线程分配策略
ps:给一个对象分配空间时,其实不是原子操作的,需要一下几步:
- 查找空闲列表(这里我们假设大多数情况下内存都是非规整的以示一般性)
- 分配内存
- 修改内存列表 等操作
第一种多线程分配策略:CAS
jvm通过cas和失败重试机制保证分配内存之后的更新等操作的原子性。
第二种多线程分配策略:TLAB
每个线程会在java堆中分配一个内存[这个内存比较小],这个内存称之为本地线程分配缓冲区[TLAB],线程内部如果需要分配内存会首先在这个TALB上分配,避免线程冲突,只有缓冲区用完之后才会在分配内存时通过CAS操作分配更大的内存空间。jdk1.5之后默认开启TLAB,当然可以手动关闭:-xx:+/-UseTLAB进行配置。
我们正在这里先讨论到这里,下次继续补齐剩余内容,当然在以上内容中我们有抛出了一些新的问题:
- volatile关键词的同步机制 有哪些优缺点呢?
- java中的悲观锁除了Synchronized之外还有哪些?
- 乐观锁的实现机制是什么?
- 线程同步策略有哪些?
以上内容我们后续在继续攻破!!!嘿嘿,奸计得逞,你以为你学会了一个东西,其实你是踩到坑里了,哈哈。请叫我坑王
。最后以上内容中可能有不详之处,大家观看时一定要存疑。
书上、别人的不一定是对的,自己测试才能测出真理。
实践是检验真理的的唯一途径
下篇内容地址:https://www.jianshu.com/p/fbab74f4fa74
版权声明:本文为原创文章,未经博主允许不责转载。
原文地址:https://www.jianshu.com/p/15f17da87ba6
参考文档: