并发设计模式 之 CAS算法

简介: 并发设计模式 之 CAS算法

对于并发控制而言,我们平时用的锁(synchronized,Lock)是一种悲观的策略。它总是假设每一次临界区操作会产生冲突,因此,必须对每次操作都小心翼翼。如果多个线程同时访问临界区资源,就宁可牺牲性能让线程进行等待,所以锁会阻塞线程执行。

与之相对的有一种乐观的策略,它会假设对资源的访问是没有冲突的。既然没有冲突也就无需等待了,所有的线程都在不停顿的状态下持续执行。那如果遇到问题了无锁的策略使用一种叫做比较交换(CAS Compare And Swap)来鉴别线程冲突,一旦检测到冲突产生,就重试当前操作直到没有冲突。CAS算法是非阻塞的,它对死锁问题天生免疫,而且它比基于锁的方式拥有更优越的性能。

CAS算法的过程是这样:它包含三个参数 CAS(V,E,N)。V表示要更新的变量,E表示预期的值,N表示新值。仅当V值等于E值时,才会将V的值设置成N,否则什么都不做。最后CAS返回当前V的值。CAS算法需要你额外给出一个期望值,也就是你认为现在变量应该是什么样子,如果变量不是你想象的那样,那说明已经被别人修改过。你就重新读取,再次尝试修改即可。

JDK并发包有一个atomic包,里面实现了一些直接使用CAS操作的线程安全的类型。其中最常用的一个类应该就是AtomicInteger。我们以此为例来研究一下没有锁的情况下如何做到线程安全。

private volatile int value;

这是AtomicInteger类的核心字段,代表当前实际取值,借助volatile保证线程间数据的可见性。

获取内部数据的方法:

public final int get() {
  return value;
}

我们从源码的实现看看incrementAndGet()的内部实现

public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
        }
}

代码第二行使用了一个死循环,原因是:CAS的操作未必都是成功的,因此对于不成功的情况,我们就需要进行不断的尝试。第三行取得当前值,接着+1得到新值next。这里我们使用CAS必需的两个参数:期望值以及新值。使用compareAndSet()将新值next写入。成功的条件是在写入的时刻当前的值应该要等于刚刚取到的current。如果不是这样则说明AtomicInteger的值在第3行到第5行之间被其他线程修改过了。当前看到的状态是一个过期的状态,因此返回失败,需要进行下一次重试,直到成功为止。

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

整体的过程就是这样子,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。大概的逻辑应该是这样:

if (this == expect) {
    this = update returntrue;
} else {
    returnfalse;
}

CAS虽然能高效的解决原子问题,但是CAS也会带来1个经典问题即ABA问题:

因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。

ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类在内部不仅维护了对象值,还维护了一个时间戳(可以是任意的一个整数来表示状态值)。当设置对象值时,对象值和状态值都必须满足期望值才会写入成功。因此即使对象被反复读写,写会原值,只要状态值发生变化,就能防止不恰当的写入。

/**
 * @param expectedReference 期望值
 * @param newReference 写入新值
 * @param expectedStamp 期望状态值
 * @param newStamp 新状态值
 * @return true if successful
 */
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
        int expectedStamp,
        int newStamp) {
        Pair<V> current = pair;
  return expectedReference == current.reference &&
    expectedStamp == current.stamp &&
     ((newReference == current.reference &&
    newStamp == current.stamp) ||
    casPair(current, Pair.of(newReference, newStamp)));
    }
目录
相关文章
|
7月前
|
数据采集 运维 监控
序列挖掘模式算法:提升企业电脑监控软件安全性的创新路径
当谈到提升企业电脑监控软件的安全性时,咱们不妨考虑一下序列模式挖掘算法,它们其实就是电脑监控软件的&quot;秘密武器&quot;,能够帮助我们识别和分析用户以及系统行为中的种种奇奇怪怪的模式。这可不是为了解密谜题,而是为了更好地抓住那些异常活动和潜在的安全威胁。下面我们来看看如何用序列模式挖掘算法来提高企业电脑监控软件的安全性——
129 0
|
2月前
|
算法 测试技术 C++
【数据结构】模式匹配之KMP算法与Bug日志—C/C++实现
【数据结构】模式匹配之KMP算法与Bug日志—C/C++实现
37 0
|
4月前
|
消息中间件 算法 Java
三面“有赞”Java岗斩获offer:Spring+JVM+并发锁+分布式+算法
年末离职,年初为面试也筹备挺长一段时间,找了不少复习资料,刷了很多题在网上投了很多简历最终面试了有赞,还有幸拿到offer!
|
4月前
|
机器学习/深度学习 算法 数据挖掘
【数据挖掘】关联模式评估方法及Apriori算法超市购物应用实战(超详细 附源码)
【数据挖掘】关联模式评估方法及Apriori算法超市购物应用实战(超详细 附源码)
51 0
|
4月前
|
数据采集 算法 前端开发
【MATLAB】 稳健的经验模式分解REMD信号分解算法
【MATLAB】 稳健的经验模式分解REMD信号分解算法
63 0
|
4月前
|
消息中间件 算法 NoSQL
45k以上突击面试必备,redis+mysql+并发+spring+算法+导图等
今天小编给大家带来的一篇关于Java面试相关的电子文档资源,介绍了关于Java、面试题方面的内容,本书是由Java官网出版,格式为DOC,资源大小62.5 MB,目前豆瓣、亚马逊、当当、京东等电子书综合评分为:8.7。
|
4月前
|
算法 测试技术 C#
C++单调向量算法:132模式枚举1简洁版
C++单调向量算法:132模式枚举1简洁版
|
4月前
|
算法 测试技术 C#
C++二分查找算法:132模式枚举3简洁版
C++二分查找算法:132模式枚举3简洁版
|
4月前
|
算法 测试技术 C#
C++单调向量算法:132 模式解法三枚举1
C++单调向量算法:132 模式解法三枚举1
|
4月前
|
算法 程序员 C#
C++二分查找算法:132 模式枚举3
C++二分查找算法:132 模式枚举3