并发编程-硬件加持的CAS操作够快么?
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操作够快么?的更多相关文章
- Netty的并发编程实践3:CAS指令和原子类
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能的额外损耗,因此这种同步被称为阻塞同步,它属于一种悲观的并发策略,我们称之为悲观锁.随着硬件和操作系统指令集的发展和优化,产生了非阻塞同步,被称为 ...
- Java并发编程:什么是CAS?这回总算知道了
无锁的思想 众所周知,Java中对并发控制的最常见方法就是锁,锁能保证同一时刻只能有一个线程访问临界区的资源,从而实现线程安全.然而,锁虽然有效,但采用的是一种悲观的策略.它假设每一次对临界区资源的访 ...
- Java并发编程总结2——慎用CAS(转)
一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实 ...
- Java并发编程总结2——慎用CAS
一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实 ...
- java并发编程(十七)内存操作总结
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17377197 主内存与工作内存 Java内存模型的主要目标是定义程序中各个变量的访问规则, ...
- 【Java并发编程】从CPU缓存模型到JMM来理解volatile关键字
目录 并发编程三大特性 原子性 可见性 有序性 CPU缓存模型是什么 高速缓存为何出现? 缓存一致性问题 如何解决缓存不一致 JMM内存模型是什么 JMM的规定 Java对三大特性的保证 原子性 可见 ...
- 并发编程CAS操作
并发编程CAS操作 简介 CAS即compare and swap,中文就是比较并交换 CAS是Java并发包的基石 原理 其实CAS的原理相对来说比较简单.将要被改变的数据和期望的值作比较,当两个值 ...
- 【Java并发编程实战】-----“J.U.C”:CAS操作
CAS,即Compare and Swap,中文翻译为"比较并交换". 对于JUC包中,CAS理论是实现整个java并发包的基石.从整体来看,concurrent包的实现示意图如下 ...
- Go并发编程之美-CAS操作
摘要: 一.前言 go语言类似Java JUC包也提供了一些列用于多线程之间进行同步的措施,比如低级的同步措施有 锁.CAS.原子变量操作类.相比Java来说go提供了独特的基于通道的同步措施.本节我 ...
随机推荐
- css 文字间距
letter-spacing : 字与字之间的距离 text-indent : 行的抬头间距 line-height : 行高度
- PMBOK(第六版) PMP笔记——《十一》第十一章(项目风险管理)
PMBOK(第六版) PMP笔记——<十一>第十一章(项目风险管理) 第十一章 风险管理: 项目的独特性导致项目充满风险,项目风险是一种不确定的事件或条件,可能发生.将 要发生,也可能不发 ...
- 实用---eclipse中的代码提示功能
Eclipse 的代码提示功能,具体配置 1. 打开Eclipse ,然后“window”→“Preferences” 2. 选择“java”,展开,“Editor”,选择“Content Assis ...
- Centos7安装moloch步骤
Centos7安装moloch步骤 Moloch 是一个由AOL开源的,能够大规模的捕获IPv4数据包(PCAP).索引和数据库系统,由以下三个部分组成: capture :绑定interface ...
- 10.Nginx流行架构LNMP
1.什么是LNMP架构 LNMP是指一组通常一起使用来运行动态网站或者服务器的自由软件名称首字母缩写.L指Linux,N指Nginx,M一般指MySQL,也可以指MariaDB,P一般指PHP,也可以 ...
- js检测页面上一个元素是否已经滚动到了屏幕的可视区域内
应用场景:只要页面加载了,其中在页面中出现的li就向控制台输出第几个发送请求:在本次加载的页面中,再将滚动条滚回前边的li,不再向控制台输出东西,也就是说已经显示过的li,不再向控制台输出东西. &l ...
- Nmap渗透测试使用方法
Nmap渗透测试使用方法 目标选择2 端口选择2 操作系统和服务检测2 Nmap输出格式2 用NSE脚本深入挖掘2 HTTP服务信息3 检测SSL漏洞问题的主机3 设备扫描3 按VNC扫描5 按SMB ...
- GO基础之数组
一.数组的声明与遍历 package main import "fmt" //声明数组的形式1 ]int ], , , } func main() { // 声明数组的形式2 a ...
- 设计模式(十四)Chain of Responsibility模式
Chain of Responsibility模式就是当外部请求程序进行某个处理,但程序暂时无法直接决定由哪个对象负责处理时,就需要推卸责任.也就是说,当一个人被要求做什么事时,如果他可以做就自己做, ...
- Spring框架学习笔记(6)——阿里云服务器部署Spring Boot项目(jar包)
最近接外包,需要部署服务器,便是参考了网上的几篇博文,成功在阿里云服务器成功部署了Spring Boot项目,特记下本篇笔记 Spring Boot项目打包 这里说一下部署的一些问题 1.mysql驱 ...