• CAS你必须知道的几点


    概述

    CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。CAS也是现在面试经常问的问题,本文将深入的介绍CAS的原理。

    案例

    介绍CAS之前,我们先来看一个例子。

    /**
     * @author dajun
     * @date 2019/7/6
     */
    public class VolatileTest {
        
        public static volatile int race = 0;
     
        private static final int THREADS_COUNT = 20;
        
        public static void increase() {
            race++;
        }
     
        public static void main(String[] args) throws InterruptedException {
            Thread[] threads = new Thread[THREADS_COUNT];
            for (int i = 0; i < THREADS_COUNT; i++) {
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 10000; i++) {
                            increase();
                        }
                    }
                });
                threads[i].start();
            }
     
            while (Thread.activeCount() > 1) {
                Thread.yield();
            }
            System.out.println(race);
        }
    }

    这个例子有些网友反馈会进入死循环,我后面也发现了,在IDEA的RUN模式下确实会陷入死循环,通过 Thread.currentThread().getThreadGroup().list(); 代码可以打印出当前的线程情况如下:

        java.lang.ThreadGroup[name=main,maxpri=10]
            Thread[main,5,main]
            Thread[Monitor Ctrl-Break,5,main]

    可以看到,除了Main方法线程后,还有一个Monitor Ctrl-Break线程,这个线程是IDEA用来监控Ctrl-Break中断信号的线程。

    解决死循环的办法:如果是IDEA,可以使用DEBUG模式运行就可以,或者使用下面这段代码。

    import java.util.concurrent.CountDownLatch;
     
    /**
     * @author dajun
     * @date 2019/7/6
     */
    public class VolatileTest {
        
        public static volatile int race = 0;
     
        private static final int THREADS_COUNT = 20;
     
        private static CountDownLatch countDownLatch = new CountDownLatch(THREADS_COUNT);
     
        public static void increase() {
            race++;
        }
     
        public static void main(String[] args) throws InterruptedException {
            Thread[] threads = new Thread[THREADS_COUNT];
            for (int i = 0; i < THREADS_COUNT; i++) {
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 10000; i++) {
                            increase();
                        }
                        countDownLatch.countDown();
                    }
                });
                threads[i].start();
            }
            countDownLatch.await();
            System.out.println(race);
        }
    }

    CAS是什么?

    CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。

    CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

    源码分析

    上面源码分析时,提到最后调用了compareAndSwapInt方法,接着继续深入探讨该方法,该方法在Unsafe中对应的源码如下

    CAS的缺点:

    CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。

    1. 循环时间长开销很大。
    2. 只能保证一个变量的原子操作。
    3. ABA问题。

    循环时间长开销很大:

    CAS 通常是配合无限循环一起使用的,我们可以看到 getAndAddInt 方法执行时,如果 CAS 失败,会一直进行尝试。如果 CAS 长时间一直不成功,可能会给 CPU 带来很大的开销。

    只能保证一个变量的原子操作:

    当对一个变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个变量操作时,CAS 目前无法直接保证操作的原子性。但是我们可以通过以下两种办法来解决:1)使用互斥锁来保证原子性;2)将多个变量封装成对象,通过 AtomicReference 来保证原子性

    什么是ABA问题?ABA问题怎么解决?

    如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?

    如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

     

  • 相关阅读:
    JavaScript继承详解 转
    Chinese Consumer and Websites
    【转载】C#防SQL注入过滤危险字符信息
    记一次在数据库中查询:“包含”或者“仅包含”某些商品的订单的方法
    IE 6 position: relative + li 问题
    【转】c# 位操作
    基于asp.net MVC的无刷新文件上传
    C++ 类继承内存布局
    美杜杉 主动防御最新版
    [转]COM 连接点
  • 原文地址:https://www.cnblogs.com/keiyoumi520/p/13410253.html
Copyright © 2020-2023  润新知