温故知新-多线程-深入刨析CAS
- Posted by 微博@Yangsc_o
- 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
摘要
本文从CAS的基本操作开始,逐步探究CAS的实现原理,本文涉及代码使用JDK1.8版本;
CAS是什么?
CAS是Compare And Swap (Compare And Exchange) 的简称,从因为的意思也很容易理解:比较并交换。
- 先看一段代码,两个线程分别对atomicInteger加100,因为AtomicInteger是可以保证++是原子操作的,所以最终输出结果是:200
public class CasDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
new Thread(()->{
for (int i = 0; i < 100; i++) {
atomicInteger.incrementAndGet();
}
},"a").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
atomicInteger.incrementAndGet();
}
},"b").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(atomicInteger.get());
}
}
CAS是如何实现的?
- AtomicInteger类
在AtomicInteger数据定义的部分,实际存储的值是放在value中的,除此之外获取了unsafe实例,并且定义了valueOffset。再看到static块,根据加载过程,static块的加载发生于类加载的时候,是最先初始化的,这时候调用unsafe的objectFieldOffset从Atomic类文件中获取value的偏移量,那么valueOffset其实就是记录value的偏移量的。
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
...
}
- 再看一下incrementAndGet函数
- var5 = this.getIntVolatile(var1, var2); // 取出Object中偏移地址为var2的值var5;
- this.compareAndSwapInt(var1, var2, var5, var5 + var4)比较var1中偏移量为var2的值是否和var5相等?相等则更新为var5 + var4;参数换个名字应该会清晰很多:compareAndSwapInt(obj, offset, expect, update);
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 取出Object中偏移地址为var2的值var5;
var5 = this.getIntVolatile(var1, var2);
// 比较var1中偏移量为var2的值是否和var5相等?相等则更新为var5 + var4;
} while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
- 问题来了?比较并交换就是也是两个步骤,怎么能保证线程同步呢?
下载一下Hotspot源码,看到compareAndSwapInt实现的源码如图所示,发现最终调用了Atomic::cmpxchg(x, addr, e)方法。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
翻看源码(如下面两张图所示)可以看到,不同的平台有不同的实现方式;
- 在x86的架构下实现,是通过LOCK_IF_MP加锁的方式实现
- os::is_MP判断当前系统是否为多核系统,如果是就给总线加锁,所以同一芯片上的其他处理器就暂时不能通过总线访问内存,保证了该指令在多处理器环境下的原子性。
- __asm__说明是ASM汇编,__volatile__禁止编译器优化,
// Adding a lock prefix to an instruction on MP machine
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

在atomic.cpp中则是通过递归实现的;
因为根据IA64手册,X86_64架构下,不跨越cacheline的8byte读写是原子的,如果你有个指针,没有跨越cacheline,那么多线程对这个指针的复制和读取都是不需要加锁的,可以保证原子的读到这8byte;

CAS存在的问题?
ABA的问题
CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这就是CAS的ABA问题。
常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
目前在JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。循环时间长开销大
如果CAS不成功,则会原地自旋,如果长时间自旋会给CPU带来非常大的执行开销。
你的鼓励也是我创作的动力
温故知新-多线程-深入刨析CAS的更多相关文章
- 温故知新-多线程-深入刨析volatile关键词
文章目录 摘要 volatile的作用 volatile如何解决线程可见? CPU Cache CPU Cache & 主内存 缓存一致性协议 volatile如何解决指令重排序? volat ...
- 温故知新-多线程-深入刨析park、unpark
文章目录 摘要 park.unpark 看一下hotspot实现 参考 你的鼓励也是我创作的动力 Posted by 微博@Yangsc_o 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | ...
- 温故知新-多线程-深入刨析synchronized
Posted by 微博@Yangsc_o 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0 文章目录 摘要 synchroniz ...
- Orchard 刨析:Logging
最近事情比较多,有预研的,有目前正在研发的,都是很需要时间的工作,所以导致这周只写了两篇Orchard系列的文章,这边不能保证后期会很频繁的更新该系列,但我会写完这整个系列,包括后面会把正在研发的东西 ...
- Orchard 刨析:Caching
关于Orchard中的Caching组件已经有一些文章做了介绍,为了系列的完整性会再次对Caching组件进行一次介绍. 缓存的使用 在Orchard看到如下一段代码: 可以看到使用缓存的方法Get而 ...
- Orchard 刨析:导航篇
之前承诺过针对Orchard Framework写一个系列.本应该在昨天写下这篇导航篇,不过昨天比较累偷懒的去玩了两盘单机游戏哈哈.下面进入正题. 写在前面 面向读者 之前和本文一再以Orchard ...
- Learning Cocos2d-x for WP8(2)——深入刨析Hello World
原文:Learning Cocos2d-x for WP8(2)--深入刨析Hello World cocos2d-x框架 在兄弟篇Learning Cocos2d-x for XNA(1)——小窥c ...
- MapReduce源码刨析
MapReduce编程刨析: Map map函数是对一些独立元素组成的概念列表(如单词计数中每行数据形成的列表)的每一个元素进行指定的操作(如把每行数据拆分成不同单词,并把每个单词计数为1),用户可以 ...
- Apollo 刨析:Localization
九月 30 2014 11:27 上午 admin 0 Comments 今天我们来看一看Apollo中的Localization Component. 本地化在Apollo中的使用 像这样的 ...
随机推荐
- debian init
1 bash_completion 默认是没有bash 自动补齐的,比如apt-get install 后面是不会自动补齐的,这个在ubuntu里面是有的,debian 里面默认没有开启 开启方法 ...
- windows下node配置npm全局路径(踩坑)
事情的起因是:Koa要求v7.6.0以上的nodejs. 但是window环境下升级node不容易,试过npm install -g n 和n stable等命令无效,而网上推荐的nvm并不支持win ...
- 12.1面向对象编程的介绍(oop):封装,继承,多态,访问私有属性
#封装:内部对数据封装.作用:1.保护数据,防止被随意修改:2.使外部的程序不需要关注内部的构造:只需要提供接口给外部进行访问即可.#继承:一个类就相当于一个模板.通过父类,子类的方式实现不同角色的共 ...
- Appium自动化(13) - 详解 Keyboard 类里的方法和源码分析
如果你还想从头学起Appium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1693896.html 前言 Keyboard 类在 a ...
- 母牛的故事(hdu2018)——笔记待完善
思考:这道题考验的是罗辑思维,这个网址http://blog.csdn.net/sxhelijian/article/details/42609353的罗辑思维值得学习 #include<std ...
- CSS实现漂亮的小水球效果
先看效果图: 代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> ...
- Pyqt5_QComboBox
QComboBox 是一个集按钮和下拉选项于一体的控件,也称做下拉列表框 方法 addItem() 添加一个下拉选项 addItems() 从列表中添加下拉选项 Clear() 删除下拉选项集合中的所 ...
- 花6个月写的付费专栏,免费送|仿开源框架从零到一完整实现高性能、可扩展的RPC框架
作者 渡码,阿里巴巴码农,公众号:渡码 作者,专注大数据开发.数据分析和Python技术. 关注公众号 渡码 回复关键字 manis,可获取电子书.各章节和完整源代码,并且可加入读者群一起交流问题. ...
- dom4j——使用dom4j生成xml
使用org.dom4j.Element 创建xml /** * 生成Service.xml文件 * @param tran 交易对象 * @param filePath 文件夹路径 */ public ...
- 2/3/4G网络架构
1通讯网络演进 1.1 概念名词 LTE=Long Term Evolution=长期演进, 是3GPP制定的高数据率.低时延.面向分组域优化的新一代宽带移动通信标准项目. E-UTRAN 无线接入网 ...
