设为首页 加入收藏

TOP

Java高性能编程之CAS与ABA及解决方法(二)
2019-09-18 11:10:54 】 浏览:91
Tags:Java 高性能 编程 CAS ABA 解决 方法
个子线程分别执行10000次计数器+1操作。

    
    package tech.jarry.learning.netease;
    
    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    
    /**
     * @Description:
     * @Author: jarry
     */
    public class AtomicityWithCAS {
    
        // 建立全局计数器,用于观察CAS原子性特点
        volatile int k = 0;
        // 定义Unsafe引用对象
        private static Unsafe unsafe = null;
        // 定义k的内存偏移量(可以理解为k在内存中地址,当然实际与C指针的内存地址是完全不同的)
        private static long valueOffset;
    
        static {
            try {
                // 利用反射获取Unsafe实例对象(正常途径是无法获取的)
                Field field = Unsafe.class.getDeclaredField("theUnsafe");
                field.setAccessible(true);
                // 由于unsafe是静态对象,所以传入null。想想也对,毕竟不同的实例对象的非静态对象当然是不同的,当然需要传入实例对象作为参数喽。
                // 另外吐槽一句,我查看这段资料的时候,发现百度第一页的各个博客,几乎都是一样的示例代码。。。
                unsafe = (Unsafe)field.get(null);
    
                // 获取当前对象中全局计数器k的内存地址偏移
                Field kField = AtomicityWithCAS.class.getDeclaredField("k");
                kField.setAccessible(true);
                valueOffset = unsafe.objectFieldOffset(kField);
    
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 执行全局计数器k+1的方法
         */
        private void add(){
            // 当CAS执行失败时,需要重新执行相关操作,直到执行成功。故CAS是一个自旋锁。
            while(true) {
                // 获取CAS操作所需的旧值
                int current = unsafe.getIntVolatile(this,valueOffset);
                // 进行CAS操作
                if (unsafe.compareAndSwapInt(this,valueOffset,current,current+1)){
                    // 执行成功,就跳出循环
                    break;
                }
            }
        }
    
        /**
         * 为了体现效果,这里开启了100个线程循环执行add()操作
         * @throws InterruptedException
         */
        public void run() throws InterruptedException {
            for (int j = 0; j < 100; j++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        // 每个线程执行10000次add()操作
                        for (int m = 0; m< 10000; m++){
                            add();
                        }
                        System.out.println(Thread.currentThread().getName()+" has run finished !");
                    }
                }).start();
            }
    
            // 当前线程休眠2s,确保所有子线程执行完毕
            Thread.sleep(2000);
            System.out.println("k: "+k);
        }
    }

Main主函数

主线程调用同一包下的AtomicityWithNoDeal,AtomicityWithAtomic,AtomicityWithCAS三个类,观察运行效果。


    package tech.jarry.learning.netease;
    
    public class Main {
    
        public static void main(String[] args) throws InterruptedException {
    
    //      (new AtomicityWithNoDeal()).run();
            /**
             * 运行结果:
             * Thread-1 has run finished !
             * 。。。。。。(略98个线程)
             * Thread-83 has run finished !
             * i: 440239
             */
    
    //      (new AtomicityWithAtomic()).run();
            /**
             * 运行结果:
             * Thread-0 has run finished !
             * 。。。。。。(略98个线程)
             * Thread-69 has run finished !
             * atomicInteger: 1000000
             */
    
    //      (new AtomicityWithCAS()).run();
            /**
             * 运行结果:
             * Thread-1 has run finished !
             * 。。。。。。(略98个线程)
             * Thread-80 has run finished !
             * k: 1000000
             */
    
        }
    }

注释

其实上述代码中,重要的地方,我都写上了相关的注释。如果还有什么不清楚的地方,可以@我。

CAS缺点

CAS当然是有缺点的,否则就没Synchronized什么事情了。

  1. 从概念及代码示例中可以看出,当CAS操作执行失败时,会继续进入下一个循环执行,直到CAS操作执行成功,这种行为称为自旋。自旋的实现让所有线程都处于高频运行,争抢CPU执行时间的状态。如果操作长时间不成功,会带来很大的CPU资源消耗(所以Java有锁的粗化/升级)。
  2. CAS仅能针对单个变量进行操作,不能用于多个变量来实现原子操作。
  3. ABA问题。

正如,我之前所提到的,看待技术问题要找到其特性的最初来源。如第二点中CAS之所以不能支持多个变量的原子操作,是因为CAS操作的原子性来源于硬件的支撑,而硬件只支持单个变量的原子操作,故CAS只能针对单个变量的原子操作进行操作。而有些文章或代码中提到通过CAS执行多个变量的原子操作,其实本质并不是针对多个变量,而是针对这些变量的集合或者总的对象的Reference操作的。这有点抽象,举个栗子。我将通过CAS操作转变了某个数组的引用变量的指向,看起来我实现了整个数组内多个元素转变的原子操作。但实际是我通过改变当前引用变量的指向实现的,CAS的原子操作针对的是这个指向Reference。具体代码可以参照Atomic包中的AtomicIntegerArray与AtomicReference等实现。

至于第一点,细究起来有非常多的内容,如锁的粗化,自旋是否可以优化等。其实CAS的自旋操作实际是确保了一定有CAS操作在执行,但这是通过牺牲CPU实现的。举个栗子,为了能够监听硬件串口返回的消息,我通过while(true)来不断获取串口发送过来的数据,直到我获得了一个完整数据包。

话头收回来,让我们谈谈第三点-ABA问题。

ABA概念

ABA问题,说

首页 上一页 1 2 3 4 5 下一页 尾页 2/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇百度地图WEB端判断用户是否在网格.. 下一篇Java性能 -- CAS乐观锁

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目