Talk is cheap

CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论位置V的值是否等于A, 都将返回V原有的值。

CAS的含义是”我认为V的值应该是A,如果是,那我将V的值更新为B,否则不修改并告诉V的值实际是多少“

Show you my code

在单线程环境中分别使用无锁,加锁以及cas进行十组5亿次累加运算,然后打印出平均耗时。

 /**
* cas对比加锁测试
*
* @author Jann Lee
* @date 2019-11-21 0:12
**/
public class CasTest { @Test
public void test() {
long times = 500_000_000;
// 记录耗时
List<Long> elapsedTime4NoLock = new ArrayList<>(10);
List<Long> elapsedTime4Synchronized = new ArrayList<>(10);
List<Long> elapsedTime4ReentrantLock = new ArrayList<>(10);
List<Long> elapsedTime4Cas = new ArrayList<>(10); // 进行10组试验
for (int j = 0; j < 10; j++) {
// 无锁
long startTime = System.currentTimeMillis();
for (long i = 0; i < times; i++) {
}
long endTime = System.currentTimeMillis();
elapsedTime4NoLock.add(endTime - startTime); // synchronized 关键字(隐式锁)
startTime = endTime;
for (long i = 0; i < times; ) {
i = addWithSynchronized(i);
}
endTime = System.currentTimeMillis();
elapsedTime4Synchronized.add(endTime - startTime); // ReentrantLock 显式锁
startTime = endTime;
ReentrantLock lock = new ReentrantLock();
for (long i = 0; i < times; ) {
i = addWithReentrantLock(i, lock);
}
endTime = System.currentTimeMillis();
elapsedTime4ReentrantLock.add(endTime - startTime); // cas(AtomicLong底层是用cas实现)
startTime = endTime;
AtomicLong atomicLong = new AtomicLong();
while (atomicLong.getAndIncrement() < times) {
}
endTime = System.currentTimeMillis();
elapsedTime4Cas.add(endTime - startTime);
} System.out.println("无锁计算耗时: " + average(elapsedTime4NoLock) + "ms");
System.out.println("synchronized计算耗时: " + average(elapsedTime4Synchronized) + "ms");
System.out.println("ReentrantLock计算耗时: " + average(elapsedTime4ReentrantLock) + "ms");
System.out.println("cas计算耗时: " + average(elapsedTime4Cas) + "ms"); } /**
* synchronized加锁
*/
private synchronized long addWithSynchronized(long i) {
i = i + 1;
return i;
} /**
* ReentrantLock加锁
*/
private long addWithReentrantLock(long i, Lock lock) {
lock.lock();
i = i + 1;
lock.unlock();
return i;
} /**
* 计算平均耗时
*/
private double average(Collection<Long> collection) {
return collection.stream().mapToLong(i -> i).average().orElse(0);
}
}

从案例中我们可能看出在单线程环境场景下cas的性能要高于锁相关的操作。当然,在竞争比较激烈的情况下性能可能会有所下降,因为要不断的重试和回退或者放弃操作,这也是CAS的一个缺点所在,因为这些重试,回退等操作通常用开发者来实现。

CAS的实现并非是简单的代码层面控制的,而是需要硬件的支持,因此在不同的体系架构之间执行的性能差异很大。但是一个很管用的经验法则是:在大多数处理器上,在无竞争的锁获取和释放的”快速代码路径“上的开销,大约是CAS开销的两倍。

为何CAS如此优秀

硬件加持,现代大多数处理器都从硬件层面通过一些列指令实现CompareAndSwap(比较并交换)同步原语,进而使操作系统和JVM可以直接使用这些指令实现锁和并发的数据结构。我们可以简单认为,CAS是将比较和交换合成是一个原子操作

JVM对CAS的支持, 由于Java程序运行在JVM上,所以应对不同的硬件体系架构的处理则需要JVM来实现。在不支持CAS操作的硬件上,jvm将使用自旋锁来实现。

CAS的ABA问题

cas操作让我们减少了锁带来的性能损耗,同时也给我们带来了新的麻烦-ABA问题。

在线程A读取到x的值与执行CAS操作期间,线程B对x执行了两次修改,x的值从100变成200,然后再从200变回100;而后在线程A执行CAS操作过程中并未发现x发生过变化,成功修改了x的值。由于x的值100 ->200->100,所以称之为ABA的原因。

魔高一尺道高一丈,解决ABA的问题目前最常用的办法就是给数据加上“版本号”,每次修改数据时同时改变版本号即可。

Q&A

在竞争比较激烈的情况下,CAS要进行回退,重试等操作才能得到正确的结果,那么CAS一定比加锁性能要高吗?

并发编程-硬件加持的CAS操作够快么?的更多相关文章

  1. Netty的并发编程实践3:CAS指令和原子类

    互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能的额外损耗,因此这种同步被称为阻塞同步,它属于一种悲观的并发策略,我们称之为悲观锁.随着硬件和操作系统指令集的发展和优化,产生了非阻塞同步,被称为 ...

  2. Java并发编程:什么是CAS?这回总算知道了

    无锁的思想 众所周知,Java中对并发控制的最常见方法就是锁,锁能保证同一时刻只能有一个线程访问临界区的资源,从而实现线程安全.然而,锁虽然有效,但采用的是一种悲观的策略.它假设每一次对临界区资源的访 ...

  3. Java并发编程总结2——慎用CAS(转)

    一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实 ...

  4. Java并发编程总结2——慎用CAS

    一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实 ...

  5. java并发编程(十七)内存操作总结

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17377197 主内存与工作内存 Java内存模型的主要目标是定义程序中各个变量的访问规则, ...

  6. 【Java并发编程】从CPU缓存模型到JMM来理解volatile关键字

    目录 并发编程三大特性 原子性 可见性 有序性 CPU缓存模型是什么 高速缓存为何出现? 缓存一致性问题 如何解决缓存不一致 JMM内存模型是什么 JMM的规定 Java对三大特性的保证 原子性 可见 ...

  7. 并发编程CAS操作

    并发编程CAS操作 简介 CAS即compare and swap,中文就是比较并交换 CAS是Java并发包的基石 原理 其实CAS的原理相对来说比较简单.将要被改变的数据和期望的值作比较,当两个值 ...

  8. 【Java并发编程实战】-----“J.U.C”:CAS操作

    CAS,即Compare and Swap,中文翻译为"比较并交换". 对于JUC包中,CAS理论是实现整个java并发包的基石.从整体来看,concurrent包的实现示意图如下 ...

  9. Go并发编程之美-CAS操作

    摘要: 一.前言 go语言类似Java JUC包也提供了一些列用于多线程之间进行同步的措施,比如低级的同步措施有 锁.CAS.原子变量操作类.相比Java来说go提供了独特的基于通道的同步措施.本节我 ...

随机推荐

  1. vue+element 实现商品sku效果

    在网上搜索了很久,没有发现合适sku编辑的文章,只能自己写一个vue+element 的sku编辑功能.实现的效果如下图 除成本.售价.库存.货号这几个写死的属性外,可自行添加/删除商品属性,自行添加 ...

  2. 百万年薪python之路 -- Socket

    Socket 1. 为什么学习socket 你自己现在完全可以写一些小程序了,但是前面的学习和练习,我们写的代码都是在自己的电脑上运行的,虽然我们学过了模块引入,文件引入import等等,我可以在程序 ...

  3. solr学习篇(三) solr7.4 连接MySQL数据库

    目录 导入相关jar包 配置连接信息 将数据库导入到solr中 验证是否成功 创建一个Core,创建Core的方法之前已经很详细的讲解过了,如果还是不清楚请参考 solr7.4 安装配置篇: 1.导入 ...

  4. 斯坦福机器学习课程 Exercise 习题四

    Exercise 4: Logistic Regression and Newton’s Method 回顾一下线性回归 hθ(x)=θTx Logistic Regression hθ(x)=11+ ...

  5. Java基础(十五)异常(Exception)

    1.处理错误的要求 如果由于出现错误而使得某些操作没有完成,程序应该: 返回到一种安全状态,并能够让用户执行一些其他的命令. 允许用户保存所有操作的结果,并以妥善的方式终止程序. 2.程序中可能出现的 ...

  6. 选择器, ,>,+,~

    一.后代选择器 选取指定元素的后代元素 与子元素选择器相比,后代选择器选取的不一定是直接后代(儿子),而是作用于所有后代元素(儿子.孙子.重孙…)都可以. 二.(>)子元素选择器 选取某个元素的 ...

  7. LFU的基本原理与实现

    前言:之前有写过一篇关于LRU的文章链接https://www.cnblogs.com/wyq178/p/9976815.html  LRU全称:Least Recently Used:最近最少使用策 ...

  8. [Next] Next.js+Nest.js实现GitHub第三方登录

    GitHub OAuth 第三方登录 第三方登录的关键知识点就是 OAuth2.0 . 第三方登录,实质就是 OAuth 授权 . OAuth 是一个开放标准,允许用户让第三方应用访问某一个网站的资源 ...

  9. 汇编语言——物理地址=段地址x16+偏移地址,检测点2.2

    一.为什么 物理地址=段地址x16+偏移地址? 刚开始学时,我都笨到不明白为什么是2的N次方,咱把物理地址就当数字,计算机中数字是由很多位0或1自由组合的, 而每一位上要么是0要么是1,只有这两种情况 ...

  10. 【Linux】【自学笔记】docker搭建一个spring-boot程序

    写在开始    最近捣腾Linux,安装虚拟机VMware并安装了CentOS 7系统,开始研究Linux,但是无从下手,就结合工作中用到的东西一步一步研究,事实并不是那么顺利.特此开博客,记录在过程 ...