Java的Atomic原子类

农民  金牌会员 | 2023-5-26 14:39:39 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 907|帖子 907|积分 2721

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

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

基本数据类型

AtomicBoolean、AtomicLong、AtomicInteger 这三个类提供了一些对基本数据类型的变量值进行原子更新的方法。
这些类提供的方法是相似的,主要有(以 AtomicLong 为例):
  1. // 原子化的 i++
  2. long getAndIncrement()
  3. // 原子化的 i--
  4. long getAndDecrement()
  5. // 原子化的 ++i
  6. long incrementAndGet()
  7. // 原子化的 --i
  8. long decrementAndGet()
  9. // 原子化的 i+=delta,返回值为+=前的i值
  10. long getAndAdd(long delta)
  11. // 原子化的 i+=delta,返回值为+=后的i值
  12. long addAndGet(delta)
  13. // CAS操作。如果写回成功返回true,否则返回false
  14. boolean compareAndSet(long expect, long update)
  15. // 以下四个方法新值可以通过传入函数式接口(func函数)来计算
  16. long getAndUpdate(LongUnaryOperator updateFunction)
  17. long updateAndGet(LongUnaryOperator updateFunction)
  18. long getAndAccumulate(long x, LongBinaryOperator accumulatorFunction)
  19. long accumulateAndGet(long x, LongBinaryOperator accumulatorFunction)
复制代码
  1. // 演示 getAndUpdate() 方法的使用
  2. public static void main(String[] args) {
  3.     AtomicLong atomicLong = new AtomicLong(0);
  4.     long result = atomicLong.getAndUpdate(new LongUnaryOperator() {
  5.         @Override
  6.         public long applyAsLong(long operand) {
  7.             return operand + 1;
  8.         }
  9.     });
  10. }
复制代码
对象引用类型

AtomicReference、AtomicStampedReference、AtomicMarkableReference 这三个类提供了一些对对象变量的指向进行原子更新的方法。如果需要对对象的属性进行原子更像,那么可以使用原子化的对象属性更新器。
  1. public class ClassName {
  2.     AtomicReference<Employee> employeeAR = new AtomicReference<>(new Employee("小明"));
  3.     public void methodName() {
  4.         Employee oldVal = employeeAR.get();
  5.         Employee newVal = new Employee(oldVal.getName());
  6.         employeeAR.compareAndSet(oldVal, newVal);
  7.     }
  8. }
复制代码
对象引用的原子化更新需要重点关注 ABA 问题。当一个线程在进行 CAS 操作时,另一个线程可能会在此期间修改了同一个共享变量的值,然后又将其改回原来的值。这种情况下,CAS 操作就无法检测到共享变量值的变化,从而导致 ABA 问题。如果我们仅仅在写回数据前判断数值是 A,可能导致不合理的写回操作。AtomicStampedReference 和 AtomicMarkableReference 这两个原子类可以解决 ABA 问题。

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

AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray 这三个类提供了一些对数组里面的的元素进行原子更新的方法。
  1. public class ClassName {
  2.     AtomicLongArray atomicLongArray = new AtomicLongArray(new long[]{0, 1});
  3.     public void methodName() {
  4.         int index = 0;
  5.         long oldVal = atomicLongArray.get(index);
  6.         long newVal = oldVal + 1;
  7.         atomicLongArray.compareAndSet(index, oldVal, newVal);
  8.     }
  9. }
复制代码
原子化的对象属性更新器

原子化的对象属性更新器有:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater。
这三个类提供了一些对对象的属性进行原子更新的方法。这些方法是利用反射机制实现的。
  1. public class ClassName {
  2.     AtomicIntegerFieldUpdater<Employee> fieldUpdater =
  3.             AtomicIntegerFieldUpdater.newUpdater(Employee.class, "salary");
  4.     Employee employee = new Employee("小明", 1000);
  5.     public void methodName() {
  6.         int oldVal = employee.getSalary();
  7.         int newVal = oldVal + 1000;
  8.         fieldUpdater.compareAndSet(employee, oldVal, newVal);
  9.     }
  10. }
复制代码
需要注意的是:

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

原子化的累加器有:LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator。这四个类仅仅用来在多线程环境下,执行累加操作。
相比原子化的基本数据类型,原子化的累加器的速度更快,但是它(原子化的累加器)不支持 compareAndSet() 方法。如果仅仅需要累加操作,使用原子化的累加器性能会更好。
原子化的累加器的本质是空间换时间。
LongAdder 的使用示例如下所示:
  1. public static void main(String[] args) {
  2.     LongAdder adder = new LongAdder();
  3.     // 初始化
  4.     adder.add(1);
  5.     // 累加
  6.     for (int i = 0; i < 100; i++) {
  7.         adder.increment();
  8.     }
  9.     long sum = adder.sum();
  10. }
复制代码
LongAccumulator 与 LongAdder 类似,但 LongAccumulator 提供了更加灵活的累加操作,可以自定义累加函数。
使用示例如下所示。在使用示例中,我们创建了一个 LongAccumulator 对象,初始值为1,累加函数为 (x, y) -> x * y,即每次累加都将之前的结果与新的值相乘。然后,我们累加了三个数值,最后输出累加结果。由于累加函数是(x, y) -> x * y,所以最终的累加结果为1 * 5 * 10 * 20 = 1000。
  1. public static void main(String[] args) {
  2.     LongAccumulator accumulator = new LongAccumulator(new LongBinaryOperator() {
  3.         @Override
  4.         public long applyAsLong(long left, long right) {
  5.             return left * right;
  6.         }
  7.     }, 1);
  8.     // 初始值为1,累加函数为(x, y) -> x * y
  9.     accumulator.accumulate(5);
  10.     accumulator.accumulate(10);
  11.     accumulator.accumulate(20);
  12.     // 累加结果为 1 * 5 * 10 * 20 = 1000
  13.     long result = accumulator.get();
  14. }
复制代码
参考资料

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农民

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表