美文网首页
突击并发编程JUC系列-原子更新AtomicLong

突击并发编程JUC系列-原子更新AtomicLong

作者: 山间木匠1 | 来源:发表于2020-09-22 10:07 被阅读0次

    突击并发编程JUC系列演示代码地址:
    https://github.com/mtcarpenter/JavaTutorial

    JavaJDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。原子类通过 CAS (compare and swap)volatilenative方法实现,比 synchronized 开销更小,执行效率更高,在多线程环境下,无锁的进行原子操作。

    Atomic包分类

    对其进行分类如下:

    v2-0287f51914e342e9861f66f7734b311e_720w.png

    为了避免一个章节内容过多,导致大家提前下车,会通过几个章节进行 Atomic 包下面的知识讲解。

    基本类型

    基本类型有AtomicBooleanAtomicIntegerAtomicLong、这 3 个类提供的方法几乎一模一样,本章节以 AtomicLong为案例进行讲解,提前小剧透为了在后面和LongAdder 进行对比,LongAdderAtomicLong 在面试中也被问到过呢。

    AtomicLong 的常用方法如下

    方法名 说明
    long getAndIncrement() 以原子方式将当前值加1,注意,返回的是旧值。(i++)
    long incrementAndGet() 以原子方式将当前值加1,注意,返回的是新值。(++i)
    long getAndDecrement() 以原子方式将当前值减 1,注意,返回的是旧值 。(i--)
    long decrementAndGet() 以原子方式将当前值减 1,注意,返回的是旧值 。(--i)
    long addAndGet(int delta) 以原子方式将输入的数值与实例中的值(AtomicLong里的value)相加,并返回结果
    long getAndSet(int newValue) 以原子方式设置为newValue的值,并返回旧值
    long get() _获取 AtomicLong 中的值(value)_
    boolean compareAndSet(int expect,int update) 如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
    void lazySet(int newValue) 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
    ........ .........
    JDK 1.8 新增
    long getAndUpdate(LongUnaryOperator updateFunction) 定函数的结果原子更新当前值,返回上一个值。
    long updateAndGet(LongUnaryOperator updateFunction) 使用给定函数的结果原子更新当前值,返回更新的值。 该功能应该是无副作用的,因为尝试的更新由于线程之间的争用而失败时可能会被重新应用。
    ........ ........

    温馨提示:i++++ii----i只是为了帮助大家理解、理解、理解,重要的事情说三遍,并不是底层的实现就是它们哟。

    小试牛刀

    古人云“是骡子是马拉出来溜溜“,一段代码撸起来,走你。

    public class AtomicExample1 {
        /**
         * 初始化为 0
         */
        private static AtomicLong count = new AtomicLong(0);
    
        private static LongUnaryOperator longUnaryOperator = new LongUnaryOperator() {
    
            @Override
            public long applyAsLong(long operand) {
                return 1;
            }
        };
    
        private static LongBinaryOperator longBinaryOperator = new LongBinaryOperator() {
            @Override
            public long applyAsLong(long left, long right) {
                return left + right;
            }
        };
    
        public static void main(String[] args) {
            // 以原子方式将当前值加1,返回旧值 (i++): 0
            System.out.println("getAndIncrement=" + count.getAndIncrement());
            // 以原子方式将当前值加1,返回新值(++i)  两次增加 : 2
            System.out.println("incrementAndGet=" + count.incrementAndGet());
            //以原子方式将当前值减少 1,返回旧值 (i--):2
            System.out.println("incrementAndGet=" + count.getAndDecrement());
            //以原子方式将当前值减少 1,返回旧值 (--i):0
            System.out.println("incrementAndGet=" + count.decrementAndGet());
            // 以原子方式将输入的数值与实例中的值(AtomicLong里的value)相加,并返回结果
            System.out.println("addAndGet=" + count.addAndGet(10));
            // 以原子方式设置为`newValue`的值,并返回旧值
            System.out.println("getAndSet=" + count.getAndSet(100));
            // 获取 atomicLong 的 value
            System.out.println("get=" + count.get());
    
            System.out.println("*********** JDK 1.8 ***********");
            // 使用将给定函数定函数的结果原子更新当前值,返回上一个值
            // count.get() 为 1:返回 1
            System.out.println("getAndUpdate=" + count.getAndUpdate(longUnaryOperator));
            // 返回 applyAsLong 得值
            System.out.println("getAndUpdate=" + count.getAndUpdate(longUnaryOperator));
    
            // 获取 atomicLong 的 value
            System.out.println("get=" + count.get());
    
            // 使用给定函数应用给当前值和给定值的结果原子更新当前值,返回上一个值
            // 返回结果 1,上次结果
            System.out.println("getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
            // 返回结果 3 ,上次结果 1 + 2
            System.out.println("getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
            // 获取 atomicLong 的 value
            System.out.println("get=" + count.get());
        }
    }
    

    一串代码送给你,运行结果请参考:

    getAndIncrement=0
    incrementAndGet=2
    incrementAndGet=2
    incrementAndGet=0
    addAndGet=10
    getAndSet=10
    get=100
    *********** JDK 1.8 ***********
    getAndUpdate=100
    getAndUpdate=1
    get=1
    getAndAccumulate=1
    getAndAccumulate=3
    get=5
    

    不安全并发计数

    public class AtomicExample2 {
    
        // 请求总数
        public static int requestTotal = 1000;
    
    
        public static int count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
            long start = System.currentTimeMillis();
            for (int i = 0; i < requestTotal; i++) {
                new Thread(() -> {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    add();
                    countDownLatch.countDown();
                }).start();
    
            }
            countDownLatch.await();
            System.out.println("count=" + count);
            System.out.println("耗时:" + (System.currentTimeMillis() - start));
    
        }
    
        private static void add() {
            ++count;
        }
    }
    

    懵懂少年是否对 CountDownLatch 有疑问吗?<br />CountDownLatch 又称 倒计数器 , 也就是让一个线程或者多个线程等待其他线程结束后再继续自己的操作,类似加强版 join()

    • countDown : 执行一次, 计数器的数值 -1。
    • await :等待计算器的值为 0,才进行后面的操作,就像一个栅栏一样。

    AtomicLong 实现并发计数

    public class AtomicExample3  {
    
        // 请求总数
        public static int requestTotal = 5000;
    
        public static AtomicLong count = new AtomicLong(0);
    
    
        public static void main(String[] args) throws InterruptedException {
            final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
            long start = System.currentTimeMillis();
            for (int i = 0; i < requestTotal; i++) {
                new Thread(() -> {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    add();
                    countDownLatch.countDown();
                }).start();
    
            }
            countDownLatch.await();
            System.out.println("count=" + count.get());
            System.out.println("耗时:" + (System.currentTimeMillis() - start));
            count.addAndGet(200);
            System.out.println("count=" + count.get());
    
        }
    
        private static void add() {
            //count.incrementAndGet();
            count.getAndIncrement();
        }
    }
    

    走进源码

    一段段小小的案例演示,已经无法满足懵懂少年了,那就加餐,加餐,下面分类介绍下面 JDk 1.7JDK1.8 的底层实现。

    在 Jdk1.7 中,AtomicLong 的关键代码如下:

            
        static {
          try {
            // 获取内存 value 内存中的地址  
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
          } catch (Exception ex) { throw new Error(ex); }
        }
    
        // 省略其他代码.....
    
        public final long getAndIncrement() {
           for(;;)
                long current = get();
                long next = current + 1;
                if (compareAndSet(current, next))
                    return current;
            }
        }
    
        public final boolean compareAndSet(long expect, long update) {
            return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
        }
    
    

    getAndIncrement()进去乍一看,无限循环,这不就如一个痴情男孩一样,一直等待他的女神回信,不回信一直等啊等。

    • long current = get(); 获取 AtomicLong中的 value 值。<br />
    • long next = current + 1;: 在当前记录 + 1。
    • compareAndSet(current, next): 通过 compareAndSet方法来进行原子更新操作,将当前的值跟内存中的值进行比较,相等,则内存中没有被修改,直接写入新的值到主内存中,并return true,否则直接return false。

    在 Jdk1.8 中,AtomicLong 的关键代码如下:

    /**
         *  原子更新导致值
         *
         * @return 返回旧值
         */
        public final long getAndIncrement() {
            return unsafe.getAndAddLong(this, valueOffset, 1L);
        }
        // 
        public final long getAndAddLong(Object var1, long var2, long var4) {
            long var6;
            do {
                var6 = this.getLongVolatile(var1, var2);
            } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
            return var6;
        }
    
    • var1: 需要修改的类对象<br />
    • var2:修改的字段的内存地址<br />
    • var6 是修改前字段的值,若是没其余线程修改即与 var2 相等<br />
    • var6+var4: 修改后字段的值,也就是新值<br />
    • compareAndSwapLong :当字段实际值和var6值相当的时候,才会设置其为 var6+var4 。
    • this.getLongVolatile(var1, var2):获取对象obj中偏移量为offset的变量对应 volatile 语义的值。

    从上面的代码可以看出AtomicLong在 jdk 7 的循环逻辑,在 JDK 8 中原子操作类 unsafe 内置了。之所以内置应该是考虑到这个函数在其他地方也会用到,而内置可以提高复用性。<br />


    欢迎关注公众号 山间木匠 , 我是小春哥,从事 Java 后端开发,会一点前端、通过持续输出系列技术文章与文会友,如果本文能为您提供帮助,欢迎大家关注、 点赞、分享支持,我们下期再见!

    相关文章

      网友评论

          本文标题:突击并发编程JUC系列-原子更新AtomicLong

          本文链接:https://www.haomeiwen.com/subject/sxoiyktx.html