• 死磕 java原子类之终结篇(面试题)


    概览

    原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换。

    原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分,将整个操作视作一个整体是原子性的核心特征。

    在java中提供了很多原子类,笔者在此主要把这些原子类分成四大类。

    atomic

    原子更新基本类型或引用类型

    如果是基本类型,则替换其值,如果是引用,则替换其引用地址,这些类主要有:

    (1)AtomicBoolean

    原子更新布尔类型,内部使用int类型的value存储1和0表示true和false,底层也是对int类型的原子操作。

    (2)AtomicInteger

    原子更新int类型。

    (3)AtomicLong

    原子更新long类型。

    (4)AtomicReference

    原子更新引用类型,通过泛型指定要操作的类。

    (5)AtomicMarkableReference

    原子更新引用类型,内部使用Pair承载引用对象及是否被更新过的标记,避免了ABA问题。

    (6)AtomicStampedReference

    原子更新引用类型,内部使用Pair承载引用对象及更新的邮戳,避免了ABA问题。

    这几个类的操作基本类似,底层都是调用Unsafe的compareAndSwapXxx()来实现,基本用法如下:

    private static void testAtomicReference() {
        AtomicInteger atomicInteger = new AtomicInteger(1);
        atomicInteger.incrementAndGet();
        atomicInteger.getAndIncrement();
        atomicInteger.compareAndSet(3, 666);
        System.out.println(atomicInteger.get());
    
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
        atomicStampedReference.compareAndSet(1, 2, 1, 3);
        atomicStampedReference.compareAndSet(2, 666, 3, 5);
        System.out.println(atomicStampedReference.getReference());
        System.out.println(atomicStampedReference.getStamp());
    }
    

    原子更新数组中的元素

    原子更新数组中的元素,可以更新数组中指定索引位置的元素,这些类主要有:

    (1)AtomicIntegerArray

    原子更新int数组中的元素。

    (2)AtomicLongArray

    原子更新long数组中的元素。

    (3)AtomicReferenceArray

    原子更新Object数组中的元素。

    这几个类的操作基本类似,更新元素时都要指定在数组中的索引位置,基本用法如下:

    private static void testAtomicReferenceArray() {
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
        atomicIntegerArray.getAndIncrement(0);
        atomicIntegerArray.getAndAdd(1, 666);
        atomicIntegerArray.incrementAndGet(2);
        atomicIntegerArray.addAndGet(3, 666);
        atomicIntegerArray.compareAndSet(4, 0, 666);
        
        System.out.println(atomicIntegerArray.get(0));
        System.out.println(atomicIntegerArray.get(1));
        System.out.println(atomicIntegerArray.get(2));
        System.out.println(atomicIntegerArray.get(3));
        System.out.println(atomicIntegerArray.get(4));
        System.out.println(atomicIntegerArray.get(5));
    }
    

    原子更新对象中的字段

    原子更新对象中的字段,可以更新对象中指定字段名称的字段,这些类主要有:

    (1)AtomicIntegerFieldUpdater

    原子更新对象中的int类型字段。

    (2)AtomicLongFieldUpdater

    原子更新对象中的long类型字段。

    (3)AtomicReferenceFieldUpdater

    原子更新对象中的引用类型字段。

    这几个类的操作基本类似,都需要传入要更新的字段名称,基本用法如下:

    private static void testAtomicReferenceField() {
        AtomicReferenceFieldUpdater<User, String> updateName = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class,"name");
        AtomicIntegerFieldUpdater<User> updateAge = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
    
        User user = new User("tong ge", 21);
        updateName.compareAndSet(user, "tong ge", "read source code");
        updateAge.compareAndSet(user, 21, 25);
        updateAge.incrementAndGet(user);
        
        System.out.println(user);
    }
    
    private static class User {
        volatile String name;
        volatile int age;
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "name: " + name + ", age: " + age;
        }
    }
    

    高性能原子类

    高性能原子类,是java8中增加的原子类,它们使用分段的思想,把不同的线程hash到不同的段上去更新,最后再把这些段的值相加得到最终的值,这些类主要有:

    (1)Striped64

    下面四个类的父类。

    (2)LongAccumulator

    long类型的聚合器,需要传入一个long类型的二元操作,可以用来计算各种聚合操作,包括加乘等。

    (3)LongAdder

    long类型的累加器,LongAccumulator的特例,只能用来计算加法,且从0开始计算。

    (4)DoubleAccumulator

    double类型的聚合器,需要传入一个double类型的二元操作,可以用来计算各种聚合操作,包括加乘等。

    (5)DoubleAdder

    double类型的累加器,DoubleAccumulator的特例,只能用来计算加法,且从0开始计算。

    这几个类的操作基本类似,其中DoubleAccumulator和DoubleAdder底层其实也是用long来实现的,基本用法如下:

    private static void testNewAtomic() {
        LongAdder longAdder = new LongAdder();
        longAdder.increment();
        longAdder.add(666);
        System.out.println(longAdder.sum());
    
        LongAccumulator longAccumulator = new LongAccumulator((left, right)->left + right * 2, 666);
        longAccumulator.accumulate(1);
        longAccumulator.accumulate(3);
        longAccumulator.accumulate(-4);
        System.out.println(longAccumulator.get());
    }
    

    问题

    关于原子类的问题,笔者整理了大概有以下这些:

    (1)Unsafe是什么?

    (3)Unsafe为什么是不安全的?

    (4)Unsafe的实例怎么获取?

    (5)Unsafe的CAS操作?

    (6)Unsafe的阻塞/唤醒操作?

    (7)Unsafe实例化一个类?

    (8)实例化类的六种方式?

    (9)原子操作是什么?

    (10)原子操作与数据库ACID中A的关系?

    (11)AtomicInteger怎么实现原子操作的?

    (12)AtomicInteger主要解决了什么问题?

    (13)AtomicInteger有哪些缺点?

    (14)ABA是什么?

    (15)ABA的危害?

    (16)ABA的解决方法?

    (17)AtomicStampedReference是怎么解决ABA的?

    (18)实际工作中遇到过ABA问题吗?

    (19)CPU的缓存架构是怎样的?

    (20)CPU的缓存行是什么?

    (21)内存屏障又是什么?

    (22)伪共享是什么原因导致的?

    (23)怎么避免伪共享?

    (24)消除伪共享在java中的应用?

    (25)LongAdder的实现方式?

    (26)LongAdder是怎么消除伪共享的?

    (27)LongAdder与AtomicLong的性能对比?

    (28)LongAdder中的cells数组是无限扩容的吗?

    关于原子类的问题差不多就这么多,都能回答上来吗?点击下面的链接可以直接到相应的章节查看:

    死磕 java魔法类之Unsafe解析

    死磕 java原子类之AtomicInteger源码分析

    死磕 java原子类之AtomicStampedReference源码分析

    杂谈 什么是伪共享(false sharing)?

    死磕 java原子类之LongAdder源码分析

    彩蛋

    原子类系列源码分析到此就结束了,虽然分析的类比较少,但是牵涉的内容非常多,特别是操作系统底层的知识,比如CPU指令、CPU缓存架构、内存屏障等。

    下一章,我们将进入“同步系列”,同步最常见的就是各种锁了,这里会着重分析java中的各种锁、各种同步器以及分布式锁相关的内容。


    欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

    qrcode

  • 相关阅读:
    MVC MVP MVVM
    RC4 对称加密
    ARM 寻址方式
    杂项记录 arm64 的一些特性
    无向图-笔记-代码
    iOS 自定义导航栏
    ios中设置UIButton圆角,添加边框
    iOS 中UICollectionView实现各种视觉效果
    UIScrollView中UITableView
    iOS 13 适配总结
  • 原文地址:https://www.cnblogs.com/tong-yuan/p/Atomic.html
Copyright © 2020-2023  润新知