当前位置: 首页 > 科技观察

下面说说Java中的原子类

时间:2023-03-13 02:16:08 科技观察

在前面的内容中,我们已经了解了CAS的原理,所以这一节学习起来会非常轻松。本节介绍Java中的原子类是java.util.concurrent.atomic包下的对象。它们之所以具有共同的原子性,都来自于CAS,可见CAS的重要性。对原子类变量的操作不存在并发问题,也不需要使用同步手段进行并发控制。其底层实现可以保证变量的可见性和操作的原子性。一般我们可以使用AtomicInteger、AtomicLong等来实现计数器等功能,使用AtomicBoolean来实现标志等功能。原子类由JDK5提供。当时只有12个原子类。JDK8开发时,又增加了4个原子类,如下图2-25所示。红框是JDK8新加入的。图2-25Java16原子类下面对这些原子类进行分类和解释。2.10.1原子更新基本类型lAtomicBoolean:原子更新布尔类型。lAtomicInteger:原子更新整数。lAtomicLong:原子更新长整型。我们以AtomicInteger为例。AtomicIngeter的常用方法如下:nintaddAndGet(intdelta):以原子方式添加参数和实例中的值,并返回结果。nbooleancompareAndSet(intexpect,intupdate):如果输入值等于预期值,则以原子方式将值设置为输入值。nintgetAndIncrement():以原子方式将当前值自增1,然后返回自增前的值,即旧值。这种方法也是比较常用的方法,可以用来做计数器。nvoidlazySet(intnewValue):最终会被设置为newValue。使用lazySet设置值后,其他线程可能在短时间内仍然可以读取旧值。nintgetAndSet(intnewValue):以原子方式设置newValue,并返回旧值。nintincrementAndGet():同getAndIncrement,返回自增后的值。记得在讲解CAS应用的代码案例中,使用了原子自增方式。接下来看看getAndIncrement()是如何实现原子操作的。请参考2-45示例代码中AtomicInteger部分的源码。代码清单2-45AtomicInteger.javapublicfinalintgetAndIncrement(){returnunsafe.getAndAddInt(this,valueOffset,1);}publicfinalintgetAndAddInt(Objectvar1,longvar2,intvar4){intvar5;do{var5=this.getIntVolatile(var1,var2);}while(!this.compareAndSwapInt(var1,var2,var5,var5+var4));returnvar5;}我们得到了旧值,然后传入要相加的数,调用getAndAddInt()进行原子更新操作。实际核心方法是compareAndSwapInt(),使用CAS更新。我们的Unsafe只提供了3个CAS操作。另外需要注意的是AtomicBoolean将Boolean转化为整数,使用compareAndSwapInt进行操作。atomic包中的对象基本都是使用Unsafe提供的3种CAS操作方法实现的。请参考Unsafe的源码,如代码清单2-46所示。代码清单2-46Unsafe.java/***如果当前值为var4,则自动更新java变量为var5或var6*@return如果更新成功则返回true*/publicfinalnativebooleancompareAndSwapObject(Objectvar1,longvar2,Objectvar4,Objectvar5);publicfinalnativebooleancompareAndSwapInt(Objectvar1,longvar2,intvar4,intvar5);publicfinalnativebooleancompareAndSwapLong(Objectvar1,longvar2,longvar4,longvar6);2.10.2原子更新数组lAtomicIntegerArray:原子更新整数数组中的元素。lAtomicLongArray:原子更新长整型数组中的元素。lAtomicReferenceArray:原子更新引用类型数组中的元素。这三个类最常用的方法是以下两个方法:nget(intindex):获取索引为index的元素值。ncompareAndSet(inti,intexpect,intupdate):如果当前值等于预期值,则以原子方式将数组位置i处的元素设置为更新后的值。2.10.3原子更新引用类型lAtomicReference:原子更新引用类型。lAtomicReferenceFieldUpdater:Atom更新引用类型的字段。lAtomicMarkableReferce:以原子方式更新带有标记位的引用类型,可以使用构造函数更新布尔类型的标记位和引用类型。这三个类提供的方法是相似的。首先构造一个引用对象,然后将引用对象设置到Atomic类中,然后调用compareAndSet等一些方法进行原子操作。原理是基于Unsafe实现,但AtomicReferenceFieldUpdater略有不同。Update字段必须修改为volatile。下面我们使用原子引用类型写一个简单的demo,请看示例代码2-47中的代码清单2-47。{Useru1=newUser("pangHu",18);ai.set(u1);Useru2=newUser("pangPang",15);ai.compareAndSet(u1,u2);System.out.println(ai.get().getAge()+ai.get().getName());}staticclassUser{privateStringname;privateintage;//省略getter,settrt}}输出结果。15pangPang2.10.4原子更新字段类如果需要更新原子更新类中的某个字段,需要使用原子更新字段类。Atomic包提供了3个类用于原子字段更新:lAtomicIntegerFieldUpdater:原子更新整型字段更新器。lAtomicLongFieldUpdater:原子更新长整数字段的更新器。lAtomicStampedFieldUpdater:带有版本号的原子更新引用类型。这个方法比较重要。在引用类型中加入一个整型值可以控制数据的版本号,这样就可以解决CAS更新时可能出现的ABA问题。和引用类型一样,更新类的字段必须用publicvolatile修饰。2.10.5JDK8新增原子类介绍lDoubleAccumulatorlLongAccumulatorlDoubleAdderlLongAdder下面以LongAdder为例介绍并列举使用注意事项。这些类对应于AtomicLong等类的改进。例如,在高并发环境下,LongAccumulator和LongAdder比AtomicLong更高效。Atomic和Adder在低并发环境下的性能差不多。但是在高并发环境下,Adder的吞吐量明显更高,但是空间复杂度更高。LongAdder实际上是LongAccumulator的特例。调用LongAdder相当于通过以下方式调用LongAccumulator。sum()方法在没有并发的情况下被调用。如果并发使用,计数会不准确。下面的代码是一个例子。LongAdder不能替代AtomicLong。LongAdder的add()方法虽然可以原子操作,但是没有使用Unsafe的CAS算法,只是使用了CAS的思想。LongAdder实际上是LongAccumulator的特例。调用LongAdder相当于通过以下方式调用LongAccumulator。LongAccumulator提供了比LongAdder更强大的功能。在构造函数中,accumulatorFunction是一个双目运算符接口,它根据两个输入参数返回一个计算出的值。identity是LongAccumulator累加器的初始值。本文转载自微信公众号“颜琳”,可通过以下二维码关注。转载本文请联系颜琳公众号。