Atomic类

对原子类知识进行梳理。

目录

前言

一、AtomicInteger和AtomicLong

二、AtomicBoolean和AtomicReference

1.为什么需要AtomicBoolean

2.如何支持boolean和double类型

三、AtomicStapedReference和AtomicMarkableReference

1.ABA问题与解决方法

2.为什么没有AtomicStampedInteger或AtomicStampedLong

3.AtomicMarkableReference

四、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater

1.为什么需要AtomicXXXFieldUpdater

五、AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray

六、LongAdder、LongAccumulator、DoubleAdder和DoubleAccumulator

1.LongAdder原理

2.伪共享和缓存行填充

3.LongAdder核心实现

4.LongAccumulator

5. DoubleAdder和DoubleAccumulator



前言

对原子类知识进行梳理。原子类基于CAS函数实现,采用的乐观锁的实现方式。

Unsafe提供了Integer、Long、Object的CAS操作函数。

对应的类有AtomicInteger、AtomicLong、AtomicReference

对ABA问题的解决:AtomicStapedReference、AtomicMarkableReference。通过引入版本号来实现,要同时进行两个值的CAS所以只能实现引用对象的Atomic类。Staped是Long类型的版本号,Markable是Boolean类型的版本号。

AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater,对已经存在的类的成员变量进行原子操作。

AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray提供数组元素的原子操作。

LongAdder最终一致性,用于高并发且不要求强一致的情况下,采用拆分的思想。

LongAccumulator可以定义二元操作符,且可以传入一个初始值。


一、AtomicInteger和AtomicLong

对于一个整数的加减操作,要保证线程安全,需要加锁,也就是加synchronized锁。但有了Concurrent包的Atomic相关的类之后,synchronized关键字可以用AtomicInteger代替,其性能更高好。

其原理就是使用了Unsafe的CAS函数。

CAS是一种乐观锁,作者认为数据发生并发冲突的概率很小,所以读操作不上锁。等到写操作的时候,再判断数据在此期间是否被其他线程修改了。如果被其他线程修改了,就把新数据重新读出来,重复这个过程。

悲观锁:作者认为数据发生并发冲突的概率很大,所以读操作之前就上锁。

Unsafe的CAS详解:

unsafe.compareAndSwapInt(this, valueOffset, expect, update);

第一个是对象,第二个是对象的成员变量,第三个是读出来的旧值,第四个是新的值。

第二个参数是一个long类型的整数,意思是某个成员变量在对应的类中的内存偏移量

可以通过objectFieldOffset(Field var1);获取

执行CAS操作的时候不是直接操作value,而是操作valueOffset。

二、AtomicBoolean和AtomicReference

1.为什么需要AtomicBoolean

因为存在下面这种情况。

 if (flag == true) flag = false;

2.如何支持boolean和double类型

在unsafe类中,只提供了三种类型的CAS:int、long、Object

boolean:将boolean转化为int再转化回boolean。

double:将double转换为long类型再转换为double。Double.doubleToRawLongBits(Double.longBitsToDouble(b) + x)


三、AtomicStapedReference和AtomicMarkableReference

1.ABA问题与解决方法

到目前为止CAS都是基于值做比较的。如果另一个线程将值修改为B再修改回A,那么对于当前线程是无法感知到,当前线程认为该值未被修改过。这就是ABA问题。

在解决ABA问题,不仅要比较值还要比较一个版本号,这就是AtomicStapedReference所做的事情。

2.为什么没有AtomicStampedInteger或AtomicStampedLong

Integer和Long类需要同时比较两个值,进行CAS的时候需要将Integer或Long和版本号一起形成一个Pair对象,然后再进行CAS操作进行比较。

3.AtomicMarkableReference

采用的版本号是boolean类型的,无法解决ABA问题,但可以降低ABA出现的概率。

四、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater

1.为什么需要AtomicXXXFieldUpdater

如果一个类是自己写的,可以在编写的时候把成员函数定义为Atomic类型。

但是如果是一个已经存在的类,要想实现对其成员变量的原子操作,就需要AtomicXXXFieldUpdater。

五、AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray

这里并不是说对整个数组的操作是原子的,而是针对数组中一个元素的原子操作而言。

偏移量的计算使用i * scale + base。

六、LongAdder、LongAccumulator、DoubleAdder和DoubleAccumulator

1.LongAdder原理

AtomicLong内部是一个volatile long型变量,由多个线程对这个变量进行CAS操作。多个线程同时对一个变量进行CAS操作,在高并发的场景下仍不够快,如果要再提高性能,把一个变量拆成多份,变成多个变量,有些类似于ConcurrentHashMap的分段锁的例子。

如图所示,把一个变量拆成了一个base变量多个Cell,每个Cell包装了一个Long型变量。

当多个线程并发累加的时候

(1)并发程度低的话,直接加到base变量上;

(2)并发程度高的话,平摊到Cell上。

最后取值的时候再把base和这些Cell求sum运算。

 public long sum() {
       //通过累加base与cells数组中的value从而获得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求和函数中,并没有对cells[]数组加锁。在执行求和操作时可能存在其他线程修改数组里的值。它是最终一致性,而不是强一致性。

适用于高并发且不要求严格同步的场景。

2.伪共享和缓存行填充

 @sun.misc.Contended用于解决伪共享和缓存行填充。

abstract class Striped64 extends Number {

    static final int NCPU = Runtime.getRuntime().availableProcessors();

    transient volatile Cell[] cells;
     
@sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

        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);
            }
        }
    }

}

缓存与主内存进行数据交互的基本单位是Cache Line缓存行。当读入了X、Y、Z三个变量时,他们可能在同一个缓存行中,修改了X变量,会导致整个缓存行失效,Y、Z变量也失效了,这就是伪共享问题

问题的原因在于X、Y、Z变量在同一个缓存行中,要解决这个问题需要用到缓存行填充,分别在X、Y、Z变成后面加上7个无用的Long型,填充整个缓存行,让X、Y、Z处于三行不同的缓存行

3.LongAdder核心实现

 public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        /**
         *  如果是第一次执行,则直接case操作base
         */
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            /**
             * as数组为空(null或者size为0)
             * 或者当前线程取模as数组大小为空
             * 或者cas更新Cell失败
             */
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

第一次尝试加在base上,第二次尝试加在Cell上。如果都失败,调用longAccumulate函数。

final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;

        if ((h = getProbe()) == 0) {
            /**
             * 若getProbe为0,说明需要初始化
             */
            ThreadLocalRandom.current(); 
            h = getProbe();
            wasUncontended = true;
        }

        //写入冲突的标志
        boolean collide = false;  

        /**
         * 失败重试
         */
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            if ((as = cells) != null && (n = as.length) > 0) {
                /**
                 *  若as数组已经初始化,(n-1) & h 即为取模操作,相对 % 效率要更高
                 */
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {       
                        Cell r = new Cell(x);   
                        if (cellsBusy == 0 && casCellsBusy()) {//这里casCellsBusy的作用其实就是一个spin lock
                            //可能会有多个线程执行了`Cell r = new Cell(x);`,
                            //因此这里进行cas操作,避免线程安全的问题,同时前面在判断一次
                            //避免正在初始化的时其他线程再进行额外的cas操作
                            boolean created = false;
                            try {               
                                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 现在是非空了,continue到下次循环重试
                        }
                    }
                    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;//若cas更新成功则跳出循环,否则继续重试
                else if (n >= NCPU || cells != as) // 最大只能扩容到CPU数目, 或者是已经扩容成功,这里只有的本地引用as已经过期了
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as) {      // 扩容
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                //重新计算hash(异或)从而尝试找到下一个空的slot
                h = advanceProbe(h);
            }
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        /**
                         * 默认size为2
                         */
                        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 : // 若已经有另一个线程在初始化,那么尝试直接更新base
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

初始化时容量为2,每次扩容扩大为2倍,不能超过CPU数量。

4.LongAccumulator

和LongAdder的区别就在于下面一句话

 else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))

LongAccumulator支持自定义一个二元操作符,并且可以传入一个初始值。

5. DoubleAdder和DoubleAccumulator

DoubleAdder其实也是用Long型实现的,因为没有double类型的CAS函数。

Double.doubleToRawLongBits(Double.longBitsToDouble(b) + x)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值