21-原子类

nobility 发布于 2021-06-12 2449 次阅读


原子类

原子类的作用:保证一个操作能够在多线程的情况下是不可中断的、不可分隔的,Java中的原子类存放与java.util.concurrent.atomic包中

相对于锁有如下优势:

  • 粒度更细:原子变量的竞争范围是变量级别
  • 效率较高:除啦在高度竞争的情况下,使用原子类的效率会比使用锁的效率更高

基本类型原子类

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean

AtomicInteger为例有以下方法

方法名 描述
public final int get() 获取当前值
public final void set(int newValue) 设置当前值
public final int getAndSet(int newValue) 获取当前值,并同时设置新值
public final int getAndIncrement() 获取当前值,并同时自增1
public final int getAndDecrement() 获取当前值,并同时自减1
public final int getAndAdd(int delta) 获取当前值,并同时加指定数
public final boolean compareAndSet(int expectedValue, int newValue) 若当前值的是expectedValue预期值,则以原子方式将该值设置为newValue新值,并返回是否更新成功

数组类型原子类

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

AtomicIntegerArray为例有以下方法:构造方法传入数组长度,此时时数组中都是不同类型的0值

方法名 描述
public final int length() 获取数组长度
public final int get(int i) 获取i索引下的元素
public final void set(int i, int newValue) 设置i索引下的元素
public final int getAndSet(int i, int newValue) 获取i索引下的元素,并同时设置新值
public final int getAndIncrement(int i) 获取i索引下值,并同时自增1
public final int getAndDecrement(int i) 获取i索引下值,并同时自减1
public final int getAndAdd(int i, int delta) 获取i索引下值,并同时加指定数
public final boolean compareAndSet(int i, int expectedValue, int newValue) i索引下的值是expectedValue预期值,则以原子方式将该值设置为newValue新值,并返回是否更新成功

引用类型原子类

  • AtomicReference
  • AtomicStampedReference
  • AtomicMarkableReference

AtomicReference为例有以下方法

方法名 描述
public final boolean compareAndSet(V expectedValue, V newValue) 若当前值的是expectedValue预期值,则以原子方式将该值设置为newValue新值,并返回是否更新成功

升级类型原子类

  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
  • AtomicReferenceFieldUpdater

使用升级原子类的newUpdater()静态方法,传入要升级的Class对象,以及该类中的要升级的变量,也就是说该方法使用的是反射机制创建对象,所以要升级的变量必须要有以下特点

  • 不能是不可见(private)的变量
  • 不能是静态变量
  • 必须是volatile修饰的变量

该方法会返回一个将该变量升级成原子类的变量,就会具有下面方法:这些方法都需要首先将实例对象传入

方法名 描述
public int get(T obj) 获取当前值
public void set(T obj, int newValue) 设置当前值
public int getAndSet(T obj, int newValue) 获取当前值,并同时设置新值
public int getAndIncrement(T obj) 获取当前值,并同时自增1
public int getAndDecrement(T obj) 获取当前值,并同时自减1
public int getAndAdd(T obj, int delta) 获取当前值,并同时加指定数
public boolean compareAndSet(T obj, int expect, int update) 若当前值的是expectedValue预期值,则以原子方式将该值设置为newValue新值,并返回是否更新成功
class A {
  public volatile int a;
}

public class Main {
  public static AtomicIntegerFieldUpdater<A> updaterA = AtomicIntegerFieldUpdater
      .newUpdater(A.class, "a");  //传入要升级的类的Class对象和属性名

  public static void main(String[] args) throws InterruptedException {
    A a = new A();
    Runnable runnable = () -> {
      for (int i = 0; i < 1000; i++) {
        updaterA.getAndIncrement(a);  //使用升级方法时需要传入对象,类似反射的方式
      }
    };
    Thread thread1 = new Thread(runnable);
    Thread thread2 = new Thread(runnable);
    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();
    System.out.println(a.a);
  }
}

累加器

Adder累加器

  • LongAdder
  • DoubleAdder
方法名 描述
public void add(long x) 加指定值
public void increment() 自增1
public void decrement() 自减1
public long sum() 最后汇总求和,并返回

JDK8引入的,在高并发下比AtomicLong效率高,效率的提升本质上是空间换时间,具体如下

  • LongAdder在竞争激烈的情况下,每个线程对应到Cell数组上进行修改(相当于每个线程都有会存储一份独立的变量副本,最后进行多线程的计算汇总),使用hash值对应降低了冲突概率,从而提高了并发性能,是多段锁的理念;当竞争不激烈的情况下,所有线程对应同一个base变量上,与AtomicLong效率差不多
    • 缺点:若在计算汇总时进行了变量修改,可能结果不一定精确,因为sum()方法不是线程安全的
  • AtomicLong在竞争激烈的情况下,每一次修改都要从线程工作内存向主内存中进行flushrefresh所以导致耗费资源
public class Main {
  public static LongAdder longAdder = new LongAdder();
  public static AtomicLong atomicLong = new AtomicLong();

  public static void LongAdderTest() throws InterruptedException {
    ExecutorService service = Executors.newFixedThreadPool(20);
    long start = System.currentTimeMillis();  //起始时间
    for (int i = 0; i < 10000; i++) {
      service.execute(() -> {
        for (int j = 0; j < 10000; j++) {
          longAdder.increment();  //自增1
        }
      });
    }
    service.shutdown();
    if (service.awaitTermination(Integer.MAX_VALUE, TimeUnit.MILLISECONDS)) {  //主线程等待线程池任务执行结束
      long end = System.currentTimeMillis();  //结束时间
      System.out.print(longAdder.sum() + ",");  //将所有线程的计算进行汇总
      System.out.println("LongAdder耗时:" + (end - start));
    }
  }

  public static void AtomicLongTest() throws InterruptedException {
    ExecutorService service = Executors.newFixedThreadPool(20);
    long start = System.currentTimeMillis();  //起始时间
    for (int i = 0; i < 10000; i++) {
      service.execute(() -> {
        for (int j = 0; j < 10000; j++) {
          atomicLong.getAndIncrement();  //自增1
        }
      });
    }
    service.shutdown();
    if (service.awaitTermination(Integer.MAX_VALUE, TimeUnit.MILLISECONDS)) {  //主线程等待线程池任务执行结束
      long end = System.currentTimeMillis();  //结束时间
      System.out.print(atomicLong.get() + ",");  //查看最后的值
      System.out.println("AtomicLong耗时:" + (end - start));
    }
  }

  public static void main(String[] args) throws InterruptedException {
    LongAdderTest();
    AtomicLongTest();
  }
}

Accumulator累加器

  • LongAccumulator
  • DoubleAccumulator

相对于LongAdder更加通用,用法类似与归纳,可使用多线程实现并行计算

public static void main(String[] args) throws InterruptedException {
  LongAccumulator longAdder = new LongAccumulator((current, last) -> {
    return current + last;  //current是当前值  last是上一次的值  返回计算后的值
  }, 100);  //初始值
  longAdder.accumulate(1);  //累计值
  System.out.println(longAdder.get());  //101
}
此作者没有提供个人介绍
最后更新于 2021-06-12