盘点Java中基于CAS实现的原子类(下)

上一篇 / 下一篇  2022-12-06 11:18:18

  2022年度软件测试行业的趋势预测:如果你也想了解更多发展趋势,那就点击下方链接填写调查问卷吧!链接:http://vote.51testing.com/

  原子数组
  ·AtomicIntegerArray: Integer类型的原子数组
  · AtomicLongArray:Long类型的原子数组
  · AtomicReferenceArray:引用类型的原子数组
  直接上例子:
  public class AtomicIntegerArrayTest {
      public static void main(String[] args) throws Exception{
          AtomicIntegerArray array = new AtomicIntegerArray(10);
          Thread t1 = new Thread(()->{
              int index;
              for(int i=1; i<100000; i++) {
                  index = i%10; //范围0~9
                  array.incrementAndGet(index);
              }
          });
          Thread t2 = new Thread(()->{
              int index;
              for(int i=1; i<100000; i++) {
                  index = i%10; //范围0~9
                  array.decrementAndGet(index);
              }
          });
          t1.start();
          t2.start();
          Thread.sleep(5 * 1000);
          System.out.println(array.toString());
      }
  }
  两个线程同时对数组对象进行加和减的操作,最终结果都是0,说明线程安全。
  原子字段更新器
  · AtomicReferenceFieldUpdater
  · AtomicIntegerFieldUpdater
  · AtomicLongFieldUpdater
  利用字段更新器,可以针对对象的某个域(Field?)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常。
  @Data
  public class AtomicReferenceFieldUpdaterTest {
      private volatile int age = 10;
      private int age2;
      public static void main(String[] args) {
          AtomicIntegerFieldUpdater integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(AtomicReferenceFieldUpdaterTest.class, "age");
          AtomicReferenceFieldUpdaterTest ref = new AtomicReferenceFieldUpdaterTest();
          // 对volatile 的age字段+1
          integerFieldUpdater.getAndIncrement(ref);
          System.out.println(ref.getAge());
          // 修改 非volatile的age2
          integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(AtomicReferenceFieldUpdaterTest.class, "age2");
          integerFieldUpdater.getAndIncrement(ref);
      }
  }
  原子字段更新器只能更新volatile字段,它可以保证可见性,但是无法保证原子性。
  原子累加器
  原子累加器主要是用来做累加的,相关的类有LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator。
  LongAdder?是jdk1.8中引入的,它的性能要比AtomicLong方式好。
  LongAddr? 类是 LongAccumulator? 类的一个特例,只是 LongAccumulator? 提供了更强大的功能,可以自定义累加规则,当accumulatorFunction? 为 null 时就等价于 LongAddr。
  这边做个性能的对比例子。
  public class LongAdderTest {
      public static void main(String[] args) {
          System.out.println("LongAdder ...........");
          for (int i = 0; i < 5; i++) {
              addFunc(() -> new LongAdder(), adder -> adder.increment());
          }
          System.out.println("AtomicLong ...........");
          for (int i = 0; i < 5; i++) {
              addFunc(() -> new AtomicLong(), adder -> adder.getAndIncrement());
          }
      }
      private static <T> void addFunc(Supplier<T> adderSupplier, Consumer<T> action) {
          T adder = adderSupplier.get();
          long start = System.nanoTime();
          List<Thread> ts = new ArrayList<>();
          // 40个线程,每人累加 50 万
          for (int i = 0; i < 40; i++) {
              ts.add(new Thread(() -> {
                  for (int j = 0; j < 500000; j++) {
                      action.accept(adder);
                  }
              }));
          }
          ts.forEach(t -> t.start());
          ts.forEach(t -> {
              try {
                  t.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          });
          long end = System.nanoTime();
          System.out.println(adder + " cost:" + (end - start)/1000_000);
      }
  }
  主要是由于LongAdder会设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]... 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。
  总结
  本文总结了JDK中提供的各种原子类,包括基础原子类、原子引用类、原子数组类、原子字段更新器和原子累加器等。有时候,使用这些原子类的性能是比加锁要高的,特别是在读多写少的场景下。但是,不知道大家发现没有,所有的原子类操作对于一个共享变量执行操作是原子的,如果对于多个共享变量操作时,循环 CAS 就无法保证操作的原子性,还是老老实实加锁吧。

TAG: 软件开发 Java java

 

评分:0

我来说两句

Open Toolbar