在Unsafe类中,只提供了三种类型的CAS操作:int、long、Object(也就是引用类型)
AtomicInteger和AtomicLong
AtomicBoolean和AtomicReference
AtomicStampedReference和AtomicMarkableReference
但如果另外一个线程把变量的值从A改为B,再从B改回 到A,那么尽管修改过两次,可是在当前线程做CAS操作的时候,却会因为值没变而认为数据没有被其他 线程修改过,这就是所谓的ABA问题
要解决 ABA 问题,不仅要比较“值”,还要比较“版本号”,而这正是 AtomicStampedReference做的 事情, 因为这里要同时比较数据的“值”和“版本号”,而Integer型或者Long型的CAS没有办法同时比较两个 变量。 于是只能把值和版本号封装成一个对象,也就是这里面的Pair内部类,然后通过对象引用的CAS来 实现
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和 AtomicReferenceFieldUpdater
如果一个类是自己编写的,则可以在编写的时候把成员变量定义为Atomic类型。但如果是一个已经 有的类,在不能更改其源代码的情况下,要想实现对其成员变量的原子操作,就需要 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater。
AtomicIntegerFieldUpdater
@CallerSensitivepublic static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,String fieldName) {return new AtomicIntegerFieldUpdaterImpl<U>(tclass, fieldName, Reflection.getCallerClass());}/*** Protected do-nothing constructor for use by subclasses.*/protected AtomicIntegerFieldUpdater() {}
限制条件
要想使用AtomicIntegerFieldUpdater修改成员变量,成员变量必须是volatile的int类型(不能是 Integer包装类)
AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray
Concurrent包提供了AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray三个数组 元素的原子操作。注意,这里并不是说对整个数组的操作是原子的,而是针对数组中一个元素的原子操 作而言。
Striped64与LongAdder
从JDK 8开始,针对Long型的原子操作,Java又提供了LongAdder、LongAccumulator;针对 Double类型,Java提供了DoubleAdder、DoubleAccumulator。Striped64相关的类的继承层次如下图 所示。
LongAdder原理
把一个变量拆成多份,变为多个变量,有些类似于 ConcurrentHashMap 的分段锁的例子。如下图 所示,把一个Long型拆成一个base变量外加多个Cell,每个Cell包装了一个Long型变量。当多个线程并 发累加的时候,如果并发度低,就直接加到base变量上;如果并发度高,冲突大,平摊到这些Cell上。 在最后取值的时候,再把base和这些Cell求sum运算。
最终一致性
在sum求和方法中,并没有对cells[]数组加锁。也就是说,一边有线程对其执行求和操作,一边还
有线程修改数组里的值,也就是最终一致性,而不是强一致性。这也类似于ConcurrentHashMap 中的
clear()方法,一边执行清空操作,一边还有线程放入数据,clear()方法调用完毕后再读取,hash map里
面可能还有元素。因此,在LongAdder适合高并发的统计场景,而不适合要对某个 Long 型变量进行严
格同步的场景
伪共享与缓存行填充
在Cell类的定义中,用了一个独特的注解@sun.misc.Contended,这是JDK 8之后才有的,背后涉及
一个很重要的优化原理:伪共享与缓存行填充。
声明一个@jdk.internal.vm.annotation.Contended即可实现缓存行的填充。之所以这个地方要用
缓存行填充,是为了不让Cell[]数组中相邻的元素落到同一个缓存行里。
LongAccumulator
LongAccumulator的原理和LongAdder类似,只是功能更强大 LongAdder只能进行累加操作,并且初始值默认为0;LongAccumulator可以自己定义一个二元操 作符,并且可以传入一个初始值。
