• Java的Atomic原子类


    Java SDK 并发包里提供了丰富的原子类,我们可以将其分为五个类别,这五个类别提供的方法基本上是相似的,并且每个类别都有若干原子类。

    • 对基本数据类型的变量值进行原子更新;
    • 对对象变量的指向进行原子更新;
    • 对数组里面的的元素进行原子更新;
    • 原子化的对象属性更新器;
    • 原子化的累加器。

    007a32583fbf519469462fe61805eb4a.png

    基本数据类型

    AtomicBoolean、AtomicLong、AtomicInteger 这三个类提供了一些对基本数据类型的变量值进行原子更新的方法。

    这些类提供的方法是相似的,主要有(以 AtomicLong 为例):

    // 原子化的 i++
    long getAndIncrement()
    // 原子化的 i--
    long getAndDecrement()
    
    // 原子化的 ++i
    long incrementAndGet()
    // 原子化的 --i
    long decrementAndGet()
    
    // 原子化的 i+=delta,返回值为+=前的i值
    long getAndAdd(long delta)
    // 原子化的 i+=delta,返回值为+=后的i值
    long addAndGet(delta)
    
    // CAS操作。如果写回成功返回true,否则返回false
    boolean compareAndSet(long expect, long update)
    
    // 以下四个方法新值可以通过传入函数式接口(func函数)来计算
    long getAndUpdate(LongUnaryOperator updateFunction)
    long updateAndGet(LongUnaryOperator updateFunction)
    long getAndAccumulate(long x, LongBinaryOperator accumulatorFunction)
    long accumulateAndGet(long x, LongBinaryOperator accumulatorFunction)
    
    // 演示 getAndUpdate() 方法的使用
    public static void main(String[] args) {
        AtomicLong atomicLong = new AtomicLong(0);
        long result = atomicLong.getAndUpdate(new LongUnaryOperator() {
            @Override
            public long applyAsLong(long operand) {
                return operand + 1;
            }
        });
    }
    

    对象引用类型

    AtomicReference、AtomicStampedReference、AtomicMarkableReference 这三个类提供了一些对对象变量的指向进行原子更新的方法。如果需要对对象的属性进行原子更像,那么可以使用原子化的对象属性更新器。

    public class ClassName {
        AtomicReference employeeAR = new AtomicReference<>(new Employee("小明"));
    
        public void methodName() {
            Employee oldVal = employeeAR.get();
            Employee newVal = new Employee(oldVal.getName());
            employeeAR.compareAndSet(oldVal, newVal);
        }
    }
    

    对象引用的原子化更新需要重点关注 ABA 问题。当一个线程在进行 CAS 操作时,另一个线程可能会在此期间修改了同一个共享变量的值,然后又将其改回原来的值。这种情况下,CAS 操作就无法检测到共享变量值的变化,从而导致 ABA 问题。如果我们仅仅在写回数据前判断数值是 A,可能导致不合理的写回操作。AtomicStampedReference 和 AtomicMarkableReference 这两个原子类可以解决 ABA 问题。

    • AtomicStampedReference 通过为对象引用建立类似版本号(stamp)的方式,来解决 ABA 问题。AtomicStampedReference 实现的 CAS 方法增加了版本号参数
    • AtomicMarkableReference 的实现机制则更简单,将版本号简化成了一个 Boolean 值
    boolean compareAndSet(V expectedReference, V newReference, 
                              int expectedStamp, int newStamp)
    
    boolean compareAndSet(V expectedReference, V newReference,
                              boolean expectedMark, boolean newMark)
    

    数组

    AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray 这三个类提供了一些对数组里面的的元素进行原子更新的方法。

    public class ClassName {
        AtomicLongArray atomicLongArray = new AtomicLongArray(new long[]{0, 1});
    
        public void methodName() {
            int index = 0;
            long oldVal = atomicLongArray.get(index);
            long newVal = oldVal + 1;
            atomicLongArray.compareAndSet(index, oldVal, newVal);
        }
    }
    

    原子化的对象属性更新器

    原子化的对象属性更新器有:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater。

    这三个类提供了一些对对象的属性进行原子更新的方法。这些方法是利用反射机制实现的。

    public class ClassName {
        AtomicIntegerFieldUpdater fieldUpdater =
                AtomicIntegerFieldUpdater.newUpdater(Employee.class, "salary");
    
        Employee employee = new Employee("小明", 1000);
    
        public void methodName() {
            int oldVal = employee.getSalary();
            int newVal = oldVal + 1000;
            fieldUpdater.compareAndSet(employee, oldVal, newVal);
        }
    }
    

    需要注意的是:

    • 对象属性的类型必须是基本数据类型,不能是基本数据类型对应的包装类。如果对象属性的类型不是基本数据类型,newUpdater() 方法会抛出 IllegalArgumentException 运行时异常。
    • 对象的属性必须是 volatile 类型的,只有这样才能保证可见性。如果对象的属性不是 volatile 类型的,newUpdater() 方法会抛出 IllegalArgumentException 运行时异常。
    // AtomicIntegerFieldUpdater 类中的代码
    if (field.getType() != int.class) {
        throw new IllegalArgumentException("Must be integer type");
    }
    
    if (!Modifier.isVolatile(modifiers)) {
        throw new IllegalArgumentException("Must be volatile type");
    }
    

    原子化的累加器

    原子化的累加器有:LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator。这四个类仅仅用来在多线程环境下,执行累加操作。

    相比原子化的基本数据类型,原子化的累加器的速度更快,但是它(原子化的累加器)不支持 compareAndSet() 方法。如果仅仅需要累加操作,使用原子化的累加器性能会更好。

    原子化的累加器的本质是空间换时间。


    LongAdder 的使用示例如下所示:

    public static void main(String[] args) {
        LongAdder adder = new LongAdder();
        // 初始化
        adder.add(1);
        // 累加
        for (int i = 0; i < 100; i++) {
            adder.increment();
        }
        long sum = adder.sum();
    }
    

    LongAccumulator 与 LongAdder 类似,但 LongAccumulator 提供了更加灵活的累加操作,可以自定义累加函数。

    使用示例如下所示。在使用示例中,我们创建了一个 LongAccumulator 对象,初始值为1,累加函数为 (x, y) -> x * y,即每次累加都将之前的结果与新的值相乘。然后,我们累加了三个数值,最后输出累加结果。由于累加函数是(x, y) -> x * y,所以最终的累加结果为1 * 5 * 10 * 20 = 1000。

    public static void main(String[] args) {
        LongAccumulator accumulator = new LongAccumulator(new LongBinaryOperator() {
            @Override
            public long applyAsLong(long left, long right) {
                return left * right;
            }
        }, 1);
        // 初始值为1,累加函数为(x, y) -> x * y
        accumulator.accumulate(5);
        accumulator.accumulate(10);
        accumulator.accumulate(20);
        // 累加结果为 1 * 5 * 10 * 20 = 1000
        long result = accumulator.get();
    }
    

    参考资料

    21 | 原子类:无锁工具类的典范 (geekbang.org)


    __EOF__

  • 本文作者: 飞鱼
  • 本文链接: https://www.cnblogs.com/feiyu2/p/atomic.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    Linux环境下Redis 集群部署
    【Android】WebView请求HttpRequest和HttpResponse
    Word控件Spire.Doc 【文本】教程(10) ;在 word 文档中的字符或句子周围应用边框
    Mysql orchestrator高可用
    麻雀算法(SSA)优化xgboost的分类预测模型,多输入单输出模型,SSA-xgboost分类预测。
    8种超简单的Golang生成随机字符串方式
    LabVIEW​能否​像​C​语言​一样
    “哪李贵了”主播带货电商被喷,说到底还是服务问题
    Linux终端快捷键
    【ARK UI】HarmonyOS 从相册选择图片并显示到Image组件上
  • 原文地址:https://www.cnblogs.com/feiyu2/p/atomic.html