CAS操作以及jdk1.8后的优化

一、前言

说到CAS之前,先来看看乐观锁与悲观锁:

悲观锁认为:每个线程在对一数据进行操作时,都会有其他线程来并发修改,所以在获取数据的时候就上锁来进行操作,synchronized和lock就是一种悲观锁的策略。也就是先上锁再操作。

乐观锁认为:每个线程在对以数据进行操作时,没有其他线程来并发修改,这样就其实是所有线程都去读取共享区的数据,然后在本地工作内存操作,最后看共享区的数据有无被其他线程更新。如果没有则将修改后的数据写入,如果有的话就根据具体实现具体分析(报错或者自动重试)。即直接操作

我们不难得出:
悲观锁适合大量写操作的场景,先加锁可以保证写操作时数据的正确。
乐观锁适合大量读操作的场景,不加锁的特点能够使其读操作的性能大大提升。

二、什么是CAS操作

CAS操作,全称Compare and Swap,比较并交换。

CAS操作就是一个虚拟机实现的原子操作(一条硬件操作指令,不可被中断的一个或一系列操作),功能是将旧值替换为新值,如果旧值没有改变则替换成功,否则替换失败。

一般使用锁的时候,线程获取锁是一种悲观锁策略。即假设每一次在访问共享资源都会产生冲突,所以当前线程获取到锁的同时就会阻塞其他线程获取该锁。

CAS操作是一种乐观锁策略。它假设每一次在访问共享资源时都不会产生冲突,那不冲突就不会阻塞其他线程获取该锁,这样线程就不会出现阻塞停顿状态。Java使用CAS来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。线程只会收到操作失败的信号并进行原地自旋,并不会阻塞。

三、CAS操作的过程

CAS操作离不开这三个值(V, O, N):

  • V:内存地址存放的实际值
  • O:旧值
  • N:即将更新的新值

当且仅当VO相同时,即旧值和内存中实际存放的值相同,这表明该值没有被其他线程更改过,此时CAS通过原子的方式将N赋给V,并返回true。这是一个比较+更新操作,是原子操作。如果VO不相同,则该值已经被其他线程修改,不能把N赋给V,此时不进行操作,返回false。多个线程使用CAS操作一个变量时,只有一个线程会成功,并且成功更新,其余会失败(并不会阻塞其他线程)。失败的线程会重新尝试,也可以选择挂起线程。

synchronized存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。而CAS在竞争时如果失败,会进行一定的尝试,而并不是单纯的进行挂起唤醒操作,因此也叫非阻塞同步。

四、CAS的问题

CAS主要有以下三个问题:

1.ABA问题

CAS会检查共享内存的值有无变化,如果我们的共享内存值由A变成了B,可是又由B变回来了,此时CAS检查的时候发现共享内存的值并没有变化依然为A,但是实际上却是发生了变化。如果基本类型倒无所谓,引用类型就会有一些问题。

解决方案:对其进行版本控制,这样A-B-A就变成1A-2B-3A了。Java1.5后atomic包提供的AromicStampedReference来解决ABA问题,具体封装在compareAndSet()中。compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。

2.自旋时间过长

CAS是一种非阻塞同步,线程不会自己被挂起,而是不停的尝试而产生自旋现象(会死循环),自旋时间过长就会造成CPU很大的性能消耗。
解决方案请看夏庆文

3.只能保证一个共享变量的原子操作

如果对多个共享变量进行操作,CAS不能保证其原子性。
解决方案:利用对象整合多个变量,即一个类中的成员就是这几个变量,然后对这个对象进行CAS操作,这么做就能保证其原子性。atomic提供了AtomicReference来保证引用对象的原子性

五、jdk1.8对于CAS的优化

jdk1.8提供了一个LongAdder类,尝试使用分段CAS以及自动分段迁移的方式来大幅度替身多线程高并发执行CAS的性能。

1.分段CAS:

public class LongAdder extends Striped64 implements Serializable 

其继承的Striped64里面有两个重要变量:

/**
 * Table of cells. When non-null, size is a power of 2.
 * cell数组,大小总是2的幂次方
 */
transient volatile Cell[] cells;
/**
 * Base value, used mainly when there is no contention, but also as
 * a fallback during table initialization races. Updated via CAS.
 * 基本值,主要在没有争用的情况下使用,在表的初始化的时候也作为一个基础值。通过CAS更新。
 */
transient volatile long base;

如果发现并发更新的线程数量不是很多,就直接给base值进行累加。如果发现并发更新的数量过多,就开始实行分段CAS机制,系统把这些线程分配到不同的cell数组元素中。

public void add(long x) {
    Cell[] cs; long b, v; int m; Cell c;
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[getProbe() & m]) == null ||
            !(uncontended = c.cas(v = c.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}

源码大概流程就是首先通过CAS进行对base值的更新,此时只有一个线程会成功,然后保存进sum。其余的线程进行cell数组计算下标并分配,每个线程依次的对cell的元素进行累加,最后将base + sum[i] 求出最后的总和。

看一下LongAdder中的求cell数组总和的源码:

public long sum() {
    Cell[] cs = cells;
    long sum = base;
    if (cs != null) {
        for (Cell c : cs)
            if (c != null)
                sum += c.value;
    }
    return sum;
}

假设当前有80个线程进行一变量的自增操作,cell数组长度为8,则每一组都有10个线程,每一组对cell数组的其中一个元素做自增,最后cell数组8个元素的值都为10,累加得到80。这就等于80个线程对i进行了80次自增操作。

2.自动迁移机制

随着线程增多,每个cell中分配的线程数也会增多,当其中一个线程操作失败的时候,它会自动迁移到下一个cell中进行操作,这也就解决了CAS空旋转,自旋不停等待的问题。

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