Java 并发包中原子操作类原理剖析

     JUC 包提供了一系列 原子性操作类 ,这些类都是使用 非阻塞算法 CAS 实现 的,相比使用锁实现,原子性操作 在性能上 有很大提高。

一、原子操作类

    JUC包中提供了 AtomicInteger、AtomicLong 、AtomicBoolean 一系列原子性操作类,以 AtomicLong 为例,它 是原子性递增 或者 递减类,其 内部使用 Unsafe 来实现:

public class AtomicLong extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 1927816293512124184L;

    // (1)获取 Unsafe 实例
    private static final Unsafe unsafe = Unsafe.getUnsafe();

	// 获取变量 value 的偏移量
    private static final long valueOffset;

	// 判断 JVM 是否支持 Long 类型 无锁 CAS 
    static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();

    private static native boolean VMSupportsCS8();

    static {
        try {
        	// 获取 value 在 AtomicLong 中的偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicLong.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

	// 获取实际变量值,volatile 保证内存可见性
    private volatile long value;

    public AtomicLong(long initialValue) {
        value = initialValue;
    }
    ... ...

    ❓为什么(1)处 可以通过 Unsafe.getUnsafe(); 获取 Unsafe 类的实例呢 ❓ 因为 AtomicLong 类也是在 rt.jar 包下的,AtomicLong 类就是通过 BootStarp 类加载器进行加载的。
    

1、 递增 和 递减操作代码
  // 调用 unsafe 方法,原子性设置 value 值为原始值+1,返回值为递增后的值
  public final long incrementAndGet() {
        return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
    }
 // 调用 unsafe 方法,原子性设置 value 值为原始值-1,返回值为递减后的值
 public final long decrementAndGet() {
        return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
    }

  // 调用 unsafe 方法,原子性设置 value 值为原始值+1,返回值为原始值
  public final long getAndIncrement() {
        return unsafe.getAndAddLong(this, valueOffset, 1L);
    }
// 调用 unsafe 方法,原子性设置 value 值为原始值-1,返回值为原始值
public final long getAndDecrement() {
        return unsafe.getAndAddLong(this, valueOffset, -1L);
    }

    以上代码内部都是调用 Unsafe 的 getAndAddLong 方法来实现操作,该方法的第一个参数是 AtomicLong 类实例的引用;第二个参数是 value 变量;第三个参数是要设置的 第二个变量的值:

 public final long getAndAddLong(Object var1, long var2, long var4) {
        long var6;
        do {
			// (1)
            var6 = this.getLongVolatile(var1, var2);

		// 通过 CAS 操作修改变量的值
        } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

        return var6;
    }

(1):

 public native long getLongVolatile(Object var1, long var2);

    这是个本地方法,看不到具体逻辑,参考 JDK 7 的逻辑,应该是 获取到变量的当前值。
    

2、compareAndSet 方法
 public final boolean compareAndSet(long expect, long update) {
        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
    }

    可以看出,内部还是调用了 unsafe.compareAndSwapLong 方法。如果原子变量中的 value 值等于 expect,就用 update 值更新该值 并返回 true,否则 返回 false。

例👀: 通过一个多线程使用 AtomicLong 统计 0 的个数 :

import java.util.concurrent.atomic.AtomicLong;

/**
 * 两个线程各自统计自己所持数据中 0 的个数,
 * 每当找到一个 0 就会调用 AtomicLong 的原子性递增方法
 * */
public class AtomicTest {
    // 创建 Long 型原子计数器
    private static AtomicLong atomicLong=new AtomicLong();

    // 创建数据源
    private static Integer[] arrayOne=new Integer[]{0,1,2,3,0,5,6,0,56,0};
    private static Integer[] arrayTwo=new Integer[]{10,1,2,3,0,5,6,0,56,0};

    public static void main(String[] args) throws InterruptedException {
        // 线程 one 统计数组 arrayOne 中 0 的个数
        Thread threadOne=new Thread(new Runnable() {
            @Override
            public void run() {
                int size=arrayOne.length;
                for(int i=0;i<size;++i){
                    if(arrayOne[i].intValue()==0){
                        atomicLong.incrementAndGet();}
                }
            }
        });
        // 线程 two 统计数组 arrayTwo 中 0 的个数
        Thread threadTwo=new Thread(new Runnable() {
            @Override
            public void run() {
                int size=arrayTwo.length;
                for(int i=0;i<size;++i){
                    if(arrayTwo[i].intValue()==0){
                        atomicLong.incrementAndGet();
                    }
                }
            }
        });

        // 启动子线程
        threadOne.start();
        threadTwo.start();

        // 等待线程执行完毕
        threadOne.join();
        threadTwo.join();

        System.out.println("count():"+atomicLong.get());
    }
}

运行结果:
在这里插入图片描述
    如果没有原子类,实现计数器需要使用一定的同步措施,比如 synchronized 关键字等,但是这些都是阻塞算法,性能开销大。而 原子操作类都使用 CAS 非阻塞算法 ,性能更好。


    但是在高并发情况下,AtomicLong 还会存在性能问题, 因为大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的 CAS 操作会成功,这就造成大量线程竞争失败后,会通过无限循环不断进行自旋尝试 CAS 的操作,这会白白浪费 CPU 资源。 所以需要 在高并发下 性能更好的 LongAdder 类。
在这里插入图片描述


二、JDK 8 新增的原子操作类 LongAdder

    原子性递增 或 递减类 LongAdder,用来克服高并发下使用 AtomicLong 的缺点。既然 AtomicLong 的性能瓶颈是由于 过多线程同时去竞争一个变量的更新而产生的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源,就能解决性能的问题。
在这里插入图片描述

     如上所示,使用 LongAdder 时,是在内部维护了多个 Cell 变量,每个 Cell 变量里有一个初始值为 0 的 long 型变量 value , 这样,在同等并发量的情况下,争夺单个变量更新操作的线程量会减少,这变相地 减少了争夺共享资源的并发量。另外 ,多个线程在争夺同一个 Cell 原子变量时,如果失败了,它并不是在当前 Cell 上一直自旋 CAS 重试,而是尝试在其他 Cell 上进行 CAS 尝试,增加在当前线程重试 CAS 成功的可能性。最后,在获取 LongAdder 当前值时,是把所有 Cell 变量的 value 值累加后再加上 base 返回。
    默认情况下 Cell 数组是 null 【延迟初始化,懒加载】,因为 Cells 占用的内存是相对比较大的,所以一开始并不创建,需要时再创建。还有个 基值 base,当一开始判断 Cell 数组是 null 并且并发线程较少时,所有的累加操作都是对 base 变量进行的, 保持 Cell 数组的大小为 2 的 N 次方 ,在初始化时 Cell 数组中 Cell 元素个数为 2,数组里面的变量实体是 Cell 类型。Cell 类型用来减少缓存的争用,也就是解决伪共享问题。
    对于大多数孤立的多个原子操作 进行字节填充 是浪费的,因为原子性操作是无规律地分散在内存中的(也就是说 多个原子性变量的内存地址是不连续的,多个原子变量被放入同一个缓存行的可能性很小。)但是,原子性数组元素的内存地址是连续的,所以,数组内的多个元素能经常共享缓存行,因此 这里使用 @sun.misc.Contended 注解对 Cell 类型进行字节填充,这防止了数组中多个元素共享一个缓存行,提升了性能:

@sun.misc.Contended static final class Cell {
1、LongAdder 类图

在这里插入图片描述
    LongAdder 类继承自 Striped64 类,在 Striped64 类内部维护着三个变量,LongAdder 的真实值就是 base 的值 与 Cell 数组里面所有 Cell 元素中的 value 值的累加,base 是个基础值,默认为 0。
    cellsBusy 用于实现自旋锁,状态值只有 0 和 1,当创建 Cell 元素,扩容 Cell 数组 或 初始化 Cell 数组时,使用 CAS 操作该变量来保证同时只有一个线程可以进行操作👇:

/**
     * Spinlock (locked via CAS) used when resizing and/or creating Cells.
     */
    transient volatile int cellsBusy;

    

2、代码逻辑
  • Cell 构造方法:
 @sun.misc.Contended static final class Cell {

		// volatile 保证内存可见性
        volatile long value;
        
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }
  • long 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 计算总和 是 base 值 加上 累加所有 Cell 内部的 value 值,但是计算时没有对 Cell 数组进行加锁, 所以在累加过程中,可能有其他线程对 Cell 中的值进行了修改,也可能对数组进行了扩容,所以 sum 返回的值并不是很精确,其返回值不是一个调用 sum 方法时的原子快照值。

  • reset()
 public void reset() {
        Cell[] as = cells; Cell a;
        base = 0L;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    a.value = 0L;
            }
        }
    }

    可以看到 resset 是重置操作,把 base 置为 0,如果 Cell 数组有元素,则元素值被重置为 0。
    

  • add(long x)
public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))

				// (1)
                longAccumulate(x, null, uncontended);
        }
    }

    可以看到,当前线程应该访问 cells 数组哪一个 Cell 元素是通过 getProbe() & m 进行计算的,其中 m 是元素个数 -1,getProbe() 获取当前线程中变量 threadLocalRandomProbe 的值,这个值开始为 0,在(1) longAccumulate 方法中会对它进行初始化。并且当前线程通过分配的 Cell 元素 的 cas 函数来保证对 Cell 元素 value 值更新的原子性。
(1):

 final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
                              
		// 初始化 threadLocalRandomProbe 的值
        int h;
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); // force initialization
            h = getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            if ((as = cells) != null && (n = as.length) > 0) {
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale

				// 如果当前元素个数没有达到 CPU 个数并且有冲突,即 当前多个线程访问了 Cells 中同一个元素,则 扩容
                else if (!collide)
                    collide = true;

				// 初始化 Cells 数组,cellsBusy 是个标示,为 0 说明当前 cells 数组没有在被初始化或者扩容,也没有
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as) {      // Expand table unless stale
                        	// 如果 CAS 成功,容量扩容为 2 倍
                            Cell[] rs = new Cell[n << 1];

							// 复制 Cell 元素到扩容后数组
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {

						// 重置 cellsBusy 标记,这里虽然没有使用 CAS 操作,却是线程安全的,因为 cellsBusy 是 volatile 类型的,保证内存可见性
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }

				// 为 CAS 失败的线程重新计算当前线程的随机值  threadLocalRandomProbe ,以减少下次访问 cells 元素的冲突机会
                h = advanceProbe(h);
            }
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

    可以看到,对 cells 扩容是有条件的,当前 cells 元素个数小于当前机器的 CPU 个数 并且 当前多个线程访问了 cells 中同一个元素,从而导致冲突 ,使其中一个线程 CAS 失败时,才会进行扩容。为什么涉及 CPU 个数呢? 只有当每个 CPU 都运行一个线程时,才会使多线程的效果最佳,也就是说,cells 数组元素 与 CPU 个数一致时,每个 Cell 都使用 一个 CPU 处理,这时性能最佳。
    
🎭 总结:
    LongAdder 原子操作类,通过内部 cells 数组分担了高并发下 多线程同时对一个原子变量进行更新时的竞争量,让多个线程可以同时对 cells 数组里的元素进行并行的更新操作,另外,数组元素 Cell 使用 @sun.misc.Contended ,避免了 cells 数组内 多个原子变量被放入同一个缓存行,避免了伪共享,提升了性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值