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. 程序员修神之路--为什么有了SOA,我们还用微服务?

    菜菜哥,我最近需要做一个项目,老大让我用微服务的方式来做 那挺好呀,微服务现在的确很流行 我以前在别的公司都是以SOA的方式,SOA也是面向服务的方式呀 的确,微服务和SOA有相同之处 面向服务的架构 ...

  2. 从零开始搭建Electron+Vue+Webpack项目框架,一套代码,同时构建客户端、web端(一)

    摘要:随着前端技术的飞速发展,越来越多的技术领域开始被前端工程师踏足.从NodeJs问世至今,各种前端工具脚手架.服务端框架层出不穷,“全栈工程师”对于前端开发者来说,再也不只是说说而已.在NodeJ ...

  3. Distinct 实现自定义去重

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  4. Java 获取前一天的24小时

    //获取凌晨时间 public static Date getTodayStartTime(){ Calendar todayEnd = Calendar.getInstance(); todayEn ...

  5. Mybatis常见配置错误总结

    Mybatis常见配置错误总结 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionF ...

  6. Java8系列 (四) 静态方法和默认方法

    静态方法和默认方法 我们可以在 Comparator 接口的源码中, 看到大量类似下面这样的方法声明 //default关键字修饰的默认方法 default Comparator<T> t ...

  7. dom 创建时间

    下面讲述如何在页面生成一个装有日期的盒子 首先写出一个日期的函数进行赋值使用document.createElement创建一个文档节点div,然后将时间函数输出在div之中,利用document.b ...

  8. 最常用 最完整 最清晰 的git使用命令大全!

    Git 常用命令 初始化项目步骤 mkdir WebApp //创建文件夹 cd WebApp //进入到该文件夹 git init //初始化 touch README //生成README git ...

  9. c#通过libreOffice实现 office文件转pdf文件

    一.安装libreOffice 点击官网下载libreOffice 二.创建一个新的项目LibreOffice 创建一个新的项目,方便后面调用 添加下面代码 public class OfficeCo ...

  10. 学习笔记64_k邻近算法

    1 .假定已知数据的各个属性值,以及其类型,例如: 电影名称 打斗镜头 接吻镜头 电影类别 m1 3 104 爱情片 m2 2 100 爱情片 m3 1 81 爱情片 m4 2 90 爱情片 w1 1 ...