【Atomic】原子类小结

本节会介绍atomic包下的6种原子类,着重介绍AtomicInteger和LongAdder。

1 原子类

原子类,可以保证并发情况下线程安全。对于锁具有两点优势:

  • 粒度更细:将竞争范围缩小到变量级别,是最小粒度。锁粒度通常大于原子类。
  • 效率更高:通常效率高于锁,在高度竞争的情况下不如锁。【原因在后面】

在这里插入图片描述

1.1 基本类型原子类,以AtomicInteger为例(重点)

获取当前值

public final long get() 

原子类更新操作底层基于CAS实现。

// 1 如果当前值等于预期值,则原子更新值
public final boolean compareAndSet(long expect, long update) {
    
    
    return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
// 底层
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);


// 2 获取并自增
public final long getAndIncrement() {
    
    
    return unsafe.getAndAddLong(this, valueOffset, 1L);
}

public final long getAndAddLong(Object var1, long var2, long var4) {
    
    
    long var6;
    do {
    
    
        // 获取当前值
        var6 = this.getLongVolatile(var1, var2);
        // 如果当前值是var6,则更新为var6+var4;否则自旋
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

    return var6;
}
  • 数组类型原子类
    数组内所有的元素都是可以被原子更新。

  • 引用类型原子类

public final boolean compareAndSet(V expect, V update) {
    
    
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

1.2 普通变量升级原子类,FieldUpater

基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新。

public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = 
AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");

使用场景:偶尔需要一个原子get-set操作,而大部分场景只需要它是普通类型。
注意点:

  • 基于反射,private变量无法升级。
  • static修改的静态变量无法升级,会抛出异常。

1.3 Adder累加器,典型LongAdder(高并发场景用到)

LongAdder实现Number接口,但是没有定义equals、hashCode、compareTo方法,实例可变,存储key没有价值。

Java8中引入的类,在低并发场景下LongAdder与AtomicLong性能基本相同,高并发场景下LongAdder更好,本质是以空间换时间。

在竞争激烈时,LongAdder把不同的线程对应到不同的Cell上修改,降低冲突的概率,是多段锁的概念,提高并发性。
思想类似于ConcurrentHashMap分段的思想(16个segment)。

而AtomicLong在高并发多线程竞争更新一个原子变量时,只能有一个线程CAS操作成功,导致大量线程竞争失败,自旋尝试CAS操作,浪费CPU资源。线程CAS更新原子变量时,每次都做flush和refresh操作(工作内存写入到主内存、主内存写出到工作内存)。

使用场景:LongAdder适用统计求和计数场景,基本只提供了add方法,而AtomicLong还提供了cas操作。

与AtomicLong性能比较

场景:20个线程一共做10000次任务,每个任务做一万次递增操作。
LongAdder耗时在200ms以内,而AtomicLong超过2秒。性能差10多倍。

改进与原理

AtomicLong的原理是,每次更新操作都做同步,因此高并发时冲突较多,效率低。

LongAdder,每个线程都有自己的计数器。每个线程计数器不存在竞争关系,因此加和过程中,不需要同步机制,也不需要flush和refresh,同时没有公共的counter给所有线程统一计数。

使用了分段求和的理念,内部有base变量和Cell数组共同参与计数:竞争不激烈,直接累加到base变量上;竞争激烈,各个线程分散累加到自己的Cell数组中。

// sum方法源码
public long sum() {
    
    
    Cell[] as = cells; Cell a;
    long sum = base;
    if (as != null) {
    
    
        for (int i = 0; i < as.length; ++i) {
    
    
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}

注:加和过程并未加锁,累加过程中调sum()方法得到的值不一定准确。

适用场景

  1. 如前面所讲,高并发场景LongAdder性能更好
  2. LongAdder仅提供了加和相关方法,AtomicLong功能更多,例如CAS操作。

Accumulator累加器,典型LongAccumulator

更通用,可以传入表达式进行计算 使用场景:

  • 大量计算、并行计算
  • 不要求计算顺序 - 这一点可以理解为 321 = 132,与顺序没有关系 - 满足交换律

本文案例代码位置:https://gitee.com/dtyytop/advanced-java

猜你喜欢

转载自blog.csdn.net/LIZHONGPING00/article/details/114006961