Java CAS

引言

在介绍CAS之前,我们有必要先理解线程安全的三大特性

  • 原子性: 对于涉及共享变量访问的操作,该操作从其执行线程以外的任意线程来看是不可分割的,从而可以让各个线程依次串行访问,但是原子性并不保证可见性
  • 可见性: 修改共享变量时,立即将工作内存中的值同步到主存中,并使该修改对其他线程可见
  • 有序性: 禁止读取共享变量后的代码、修改共享变量前的代码重排序

CAScompare and swap的缩写,中文翻译成比较并交换。是一种用于在多线程环境下实现同步功能的机制。调用Java CAS需要三个操作数

  1. 内存中值的内存位置
  2. 预期值
  3. 新值

具体实现是通过值的内存位置取到内存中的值并与预期值比较,若相等,则将内存位置处的值替换为新值,若不相等,则不做任何操作返回false。如果大家有了解过悲观锁和乐观锁,可以发现CAS其实是一种乐观锁的实现。

使用CAS的目的

对于实现线程安全,我们用的比较多的应该是synchronized关键字,synchronized其实是一种悲观锁,锁被占用的情况会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。CAS是一种乐观锁,每次取数据都不会加锁,更新的时候会进行数据比对,有冲突的话则会自旋重试。可以看到在读操作频繁,更新频率低,冲突概率低的情况下,用CAS的话会更加合理。当然JDK1.6之后,Java对synchronized关键字来了一大波优化(自旋锁,锁消除,锁粗化,偏向锁,轻量级锁),一般情况下使用synchronized是非常稳定的。

CAS的底层实现

现在的CPU都是多核心的,多个核心通过总线来操作内存。那么这里就存在一个问题,就是如果多个核心同时操作一块内存区域,会发生什么问题呢?是的,这里数据就会出现混乱。不过这里我们可以从intel的使用手册中找到答案,对指令加lock前缀可以保证操作的原子性,可见性以及有序性。好了,底层的就不多说了,我们直接去看一下java.util.concurrent.atomic包下的原子类 AtomicInteger的源码实现

AtomicInteger源码

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();
    // value值的内存位置
    private static final long valueOffset;

    static {
        try {
            // 获取value的内存位置
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
      
      // value值 volatile 修饰 保证可见性
    private volatile int value;

    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    public AtomicInteger() {
    }

    /**
     * 获取value在内存中当前的值
     *
     * @return the current value
     */
    public final int get() {
        return value;
    }
    
    /**
     * 比较并替换 实现在unsafe.compareAndSwapInt中
     * @param expect 期望值
     * @param update 新值
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    
    /**
     * 自增 实现在unsafe.getAndAddInt中
     */
    public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
     }

}
  • Unsafe.class
public final native boolean compareAndSwapInt(Object o, long valueOffset, int expected, int value);

/**
 * 获取当前值 并加1,返回的是加1前的值
 */
public final int getAndAddInt(Object o, long valueOffset, int addValue) {
    int currentValue;
    do {
        currentValue = this.getIntVolatile(o, valueOffset);
        // 比较当前内存的值和预期值currentValue是否一致,一致的话则设置新值。但是因为当前内存中的值有可能被其他线程修改,会有和预期值不一致的情况,所以这里会循环直到 compareAndSwapInt 返回成功为止,这里的操作也称为CAS自旋
    } while(!this.compareAndSwapInt(o, valueOffset, currentValue, value + addValue));
    return value;
}

AtomicIntegerJavaInteger类型原子性操作的实现,可以看到底层都是调用了CAS compareAndSwapIntnative方法。
这里主要看一下compareAndSwapInt(Object o, long valueOffset, int expect, int update)的四个参数

  • o 当前操作的对象
  • valueOffset 操作值所在的内存位置
  • expect 期望值
  • update 新值

具体实现是将内存位置处的数值与预期数值相比较,若相等,则将内存位置处的值替换为新值。若不相等,则不做任何操作。
CAS自旋指的是替换新值失败时会进入循环,重新获取期望值,直到期望值和内存位置处的数值相等。

CAS的问题

ABA问题

提到CAS存在的问题,就不得不提ABA问题,什么是ABA问题呢?
举个例子,A是个共享变量,原值是10,线程1从内存中拿到了A,此时值为10,当线程1要对变量A进行CAS操作前,因为其他线程的操作,A从10变为了11,又从11变回了10。此时线程1对变量A执行CAS操作照道理应该是要失败的,但实际却是成功的。这是因为经过了上面的流程,在线程1看来,变量A没有发生任何变化,所以它执行CAS操作是会成功的。

要解决ABA问题,通常的解决方案给对象加上版本号,每经过一次CAS操作就更新一次版本号

总结

本文的目的主要是让自己对java并发包的基础CAS有个简单的了解,以便进行后续的源码分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容

  • 在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁 锁机制存在以下问题: (1...
    SunnyMore阅读 5,010评论 2 18
  • CAS简历 CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术 。Compare ...
    classtag阅读 4,152评论 2 37
  • 什么是悲观锁、乐观锁? synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观...
    爱情小傻蛋阅读 558评论 0 3
  • 引用地址 java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包。可见CAS的重...
    Lisy_阅读 725评论 0 14
  • 为了感谢支持我的朋友!整理了一份Java高级架构资料、Spring源码分析、Dubbo、Redis、Netty、z...
    Java耕耘者阅读 855评论 0 0