本文共享自华为云社区《学了这么久的高并发编程,连Java中的并发原子类都不知道?这也太Low了吧》,作者:冰 河。

今日咱们一起来聊聊Java中的并发原子类。在 java.util.concurrent.atomic包下有很多支持并发的原子类,某种程度上,咱们可以将其分红:根本数据类型的原子类、目标引证类型的原子类、数组类型的原子类、目标特点类型的原子类和累加器类型的原子类 五大类。

学了这么久的高并发编程,连Java中的并发原子类都不知道?

接下来,咱们就一起来看看这些并发原子类吧。

根本数据类型的原子类

根本数据类型的原子类包括:AtomicBoolean、AtomicInteger和AtomicLong。

翻开这些原子类的源码,咱们可以发现,这些原子类在运用上还是十分简略的,主要供给了如下这些比较常用的办法。

  • 原子化加1或减1操作

    //原子化的i++ getAndIncrement() //原子化的i– getAndDecrement() //原子化的++i incrementAndGet() //原子化的–i decrementAndGet()

  • 原子化添加指定的值

    //当时值+=delta,回来+=前的值 getAndAdd(delta) //当时值+=delta,回来+=后的值 addAndGet(delta)

  • CAS操作

    //CAS操作,回来原子化操作的成果是否成功 compareAndSet(expect, update)

  • 接收函数计算成果

    //成果数据可经过传入func函数来计算 getAndUpdate(func) updateAndGet(func) getAndAccumulate(x,func) accumulateAndGet(x,func)

目标引证类型的原子类

目标引证类型的原子类包括:AtomicReference、AtomicStampedReference和AtomicMarkableReference。

运用这些目标引证类型的原子类,可以实现目标引证更新的原子化。AtomicReference供给的原子化更新操作与根本数据类型的原子类供给的更新操作差不多,只不过AtomicReference供给的原子化操作常用于更新目标信息。这儿不再赘述。

需求特别注意的是:运用目标引证类型的原子类,要重点重视ABA问题。

关于ABA问题,文章的最终部分会阐明。

好在AtomicStampedReference和AtomicMarkableReference这两个原子类处理了ABA问题。

AtomicStampedReference类中的compareAndSet的办法签名如下所示。

boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)

可以看到,AtomicStampedReference类处理ABA问题的计划与达观锁的机制比较类似,实现的CAS办法添加了版本号。只要expectedReference的值与内存中的引证值持平,并且expectedStamp版本号与内存中的版本号相一起,才会将内存中的引证值更新为newReference,一起将内存中的版本号更新为newStamp。

AtomicMarkableReference类中的compareAndSet的办法签名如下所示。

boolean compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark)

可以看到,AtomicMarkableReference处理ABA问题的计划就更简略了,在compareAndSet办法中,新增了boolean类型的校验值。这些理解起来也比较简略,这儿,我也不再赘述了。

目标特点类型的原子类

目标特点类型的原子类包括:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater。

运用目标特点类型的原子类可以原子化的更新目标的特点。值得一提的是,这三个类的目标都是经过反射的方式生成的,如下是三个类的newUpdater()办法。

//AtomicIntegerFieldUpdater的newUpdater办法
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName)
//AtomicLongFieldUpdater的newUpdater办法
public static <U> AtomicLongFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName)
//AtomicReferenceFieldUpdater的newUpdater办法    
public static <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass,
                                                                Class<W> vclass,
                                                                String fieldName)

这儿,咱们不难看出,在AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater三个类的newUpdater()办法中,只要传递的Class信息,并没有传递目标的引证信息。假如要更新目标的特点,则一定要运用目标的引证,那目标的引证是在哪里传递的呢?

其实,目标的引证是在真正调用原子操作的办法时传入的。这儿,咱们就以compareAndSet()办法为例,如下所示。

//AtomicIntegerFieldUpdater的compareAndSet()办法
compareAndSet(T obj, int expect, int update) 
//AtomicLongFieldUpdater的compareAndSet()办法
compareAndSet(T obj, long expect, long update) 
//AtomicReferenceFieldUpdater的compareAndSet()办法    
compareAndSet(T obj, V expect, V update) 

可以看到,原子化的操作办法只是是多了一个目标的引证,运用起来也十分简略,这儿,我就不再赘述了。

另外,需求注意的是:运用AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater更新目标的特点时,目标特点必须是volatile类型的,只要这样才干保证可见性;假如目标特点不是volatile类型的,newUpdater()办法会抛出IllegalArgumentException这个运行时反常。

数组类型的原子类

数组类型的原子类包括:AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray。

运用数组类型的原子类可以原子化的更新数组里边的每一个元素,运用起来也十分简略,数组类型的原子类供给的原子化办法只是是在根本数据类型的原子类和目标引证类型的原子类供给的原子化办法的基础上添加了一个数组的索引参数。

例如,咱们以compareAndSet()办法为例,如下所示。

//AtomicIntegerArray的compareAndSet()办法
compareAndSet(int i, int expect, int update) 
//AtomicLongArray的compareAndSet()办法
compareAndSet(int i, long expect, long update)     
//AtomicReferenceArray的compareAndSet()办法   
compareAndSet(int i, E expect, E update) 

可以看到,原子化的操作办法只是是对多了一个数组的下标,运用起来也十分简略,这儿,我就不再赘述了。

累加器类型的原子类

累加器类型的原子类包括:DoubleAccumulator、DoubleAdder、LongAccumulator和LongAdder。

累加器类型的原子类就比较简略了:只是支持值的累加操作,不支持compareAndSet()办法。关于值的累加操作,比根本数据类型的原子类速度更快,功能更好。

运用原子类实现count+1

在并发编程范畴,一个经典的问题便是count+1问题。也便是在高并发环境下,如何保证count+1的正确性。一种计划便是在临界区加锁来保护共享变量count,可是这种方式太耗费功能了。

假如运用Java供给的原子类来处理高并发环境下count+的问题,则功能会大幅度提高。

简略的示例代码如下所示。

public class IncrementCountTest{
    private  AtomicLong count = new AtomicLong(0);
    public void incrementCountByNumber(int number){
        for(int i = 0; i < number; i++){
            count.getAndIncrement();
        }
    }
}

可以看到,原子类实现count+1问题,既没有运用synchronized锁,也没有运用Lock锁。

从本质上讲,它运用的是无锁或者是达观锁计划处理的count+问题,说的详细一点便是CAS操作。

CAS原理

CAS操作包括三个操作数:需求读写的内存方位(V)、预期原值(A)、新值(B)。假如内存方位与预期原值的A相匹配,那么将内存方位的值更新为新值B。

假如内存方位与预期原值的值不匹配,那么处理器不会做任何操作。

不管哪种状况,它都会在 CAS 指令之前回来该方位的值。(在 CAS 的一些特别状况下将仅回来 CAS 是否成功,而不提取当时值。)

简略点理解便是:方位 V 应该包括值 A;假如包括该值,则将 B 放到这个方位;否则,不要更改该方位,只回来方位V现在的值。这其实和达观锁的冲突检测+数据更新的原理是相同的。

ABA问题

由于CAS需求在操作值的时分查看下值有没有发生变化,假如没有发生变化则更新,可是假如一个值原来是A,变成了B,又变成了A,那么运用CAS进行查看时会发现它的值没有发生变化,可是实际上却变化了。

ABA问题的处理思路便是运用版本号。在变量前面追加上版本号,每次变量更新的时分把版本号加1,那么A-B-A 就会变成1A-2B-3A。

从Java1.5开端JDK的atomic包里供给的AtomicStampedReference类和AtomicMarkableReference类可以处理CAS的ABA问题。

关于AtomicStampedReference类和AtomicMarkableReference类前文有描绘,这儿不再赘述。

点击重视,第一时间了解华为云新鲜技能~