• 并发编程-硬件加持的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一定比加锁性能要高吗?

  • 相关阅读:
    微软企业库4.1学习笔记(二十六)Unity依赖注入模块3
    微软企业库4.1学习笔记(三十七)日志模块 在应用中使用日志模块
    微软企业库5.0学习笔记(三十五)数据访问模块 DataSet以及数据库事务
    微软企业库4.1学习笔记(四十一)依赖注入模块Unity 简介
    项目统一开发管理解决方案思路[项目组成员同时做很多项目的解决思路探讨]
    在moss2007WEB应用服务器上发布独立web程序时遇到的问题的解决思路
    工作流表单自定义的误区
    文档库创建的子文件夹的URL显示为 http://[机器名]/.... 导致无法正常访问的问题解决办法
    申请加入 .NET企业应用开发 博客团队请回复
    儿子照片
  • 原文地址:https://www.cnblogs.com/liqiangchn/p/11902198.html
Copyright © 2020-2023  润新知