整个Concurrent 包的层次体系
1、AtomicInteger 和 AtomicLong
1.1 悲观锁和乐观锁
悲观锁:数据发生并发冲突的概率很大,所以读操作之前就加上锁。synchronized关键字和ReentrantLock 都是悲观锁的典型例子。
乐观锁:数据发生并发冲突的概率很小,所以读操作之前不上锁。等到写操作的时候,再判断数据在此期间是否被其他线程修改了。如果被其他线程修改了,就把数据重新读出来,重复该过程;如果没有被修改,就写回去。判断数据是否被修改,同时写回新值,这两个操作要合成一个原子操作,也就是CAS(Compare And Set)。
AtomicInteger 就是典型的乐观锁
1.2 unsafe 的 cas 详解
public final boolean compareAndSet(int expect,int update){ return unsafe.compareAndSwapInt(this, valueOffset, expect,update); }
AtomicInteger 封装过的compareAndSet 有两个参数。第一个参数expect是指变量的旧值(是读出来的值,写回去的时候,希望没有被其他线程修改);第二个参数update 是指变量的新值(修改过的,没有写入的值)。当expect变量等于当前变量时,说明在修改的期间,没有其他线程对此变量进行修改,所以可以成功写入,变量被更新为update,返回true;否则返回false;
public final native boolean compareAndSwapInt(Object var1,long var2,int var4,int var5);
该函数有4个参数,第一个是对象(AtomicInteger 变量);第二个参数是对象的成员变量(AtomicInteger 里面包的int 变量value),它是long型整数,常被称为xxxOffset,意思是某个成员变量在对应类中的内存偏移量,表示该成员变量本身。
var1 | atomicInteger 变量 |
|
var2 |
xxxOfffset |
某个成员变量在对应类中的内存偏移量, 表示该成员变量本身 |
无论Unsafe还是valueOffset,都是静态的,也就是类级别的,所有对象共用的。
在转化的时候,先通过反射(getDeclaredField)获取value成员变量对应的Field对象,再通过objectFieldOffset函数转换成valueOffset。此处的valueOffset就代表了value变量本身,后面执行CAS操作的时候,不是直接操作value,而是操作valueOffset。
1.3 自旋与阻塞
当一个线程拿不到锁的时候,有两种基本的等待策略;
策略1:放弃CPU,进入阻塞状态,等待后续被唤醒,再重新被操作系统调度
策略2:不放弃CPU,空转,不断重试,也就是所谓的“自旋”。
这两种策略不是互斥的,可以结合使用。
2 AtomicBoolean 和 AtomicReference
2.1 为什么需要AtomicBoolean
if(flag == false){ flag = true; ...... }
实现compare 和 set 两个操作合在一起的原子性,这也是CAS提供的功能。
if(compareAndSet(false,true)){ ...... }
AtomicReference:
public final boolean compareAndSet(V expect, V update){ return unsafe.compareAndSwapObject(this,valueOffset,expect,update); }
2.2 如何支持boolean 类型
对于用int类型来代替的,在入参的时候,将boolean类型转换为int类型;在返回值的时候,将int类型转换成boolean类型。
3 AtomicStampedReference 和 AtomicMarkanle
3.1 ABA问题与解决方法
CAS 都是基于“值”来做比较。但如果另外一个线程把变量的值从A改为B,再从B改回到A,尽管修改过两次,可是在当前线程做CAS操作的时候,却会因为值没变而认为数据没有被其他线程修改过,这就是所谓的ABA问题。
解决方法:不仅比较值,还要比较“版本号”
3.2 为什么没有AtomicStampedInteger AtomicStampedLong
因为要同时比较"值"和"版本号",而Integer 型或者Long型的CAS没有办法同时比较这两个变量,于是只能把值和版本号封装成一个对象,然后通过对象引用的CAS来实现。
3.3 AtomicMarkableReference
Pair里面的版本号是boolean 类型的,而不是整型的累加变量。因为是boolean类型,只能有true、false两个版本号,所以并不能完全避免ABA问题,只是降低了ABA发生的概率。
3.4 为什么需要AtomicXXXFieldUpdater (AtomicIntegerUpdater,AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater)
如果一个类是自己编写的,则可以在编写的时候把成员变量定义为Atomic类型。如果一个已有的类,在不能更改其源码的情况下,要想实现对其成员变量的原子操作,就需要AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater。
3.5 限制条件
要想使用AtomicIntegerFieldUpdater 修改成员变量,成员变量必须是volatile 的int 类型(不能是Integer 包装类)
3.6 AtomicIntegerArray,AtomicLongArray 和 AtomicReferenceArray
Concurrent包提供了AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray 三个数组元素的原子操作。住:并不是说对整个数组的操作是原子的,而是针对数组中一个元素的原子操作而言。
3.6.1 使用方法
相比于AtomicInteger的getAndIncrement()函数,这里只是多了一个传入参数:数组的下标 i
public final boolean compareAndSet(int i,int expect,int update){...} public final int getAndDecrement(int i){...} public final int getAndSet(int i,int newValue){...}
3.6.2 实现原理
其底层的CAS函数用的还是 compareAndSwapInt,但是把数组下标 i 转换成对应的内存偏移量,所用的方法和之前的AtomicInteger 不太一样。
public static long byteOffset(int i){ return ((long) i << shift) + base; }
把下标 i 转换成对应的内存地址, 用到了shift 和 base 两个变量。这两个变量都是AtomicIntegerArray 的静态成员变量,用Unsafe 类的arrayBaseOffset 和 arrayIndexScale 两个函数来获取。
base 表示数组的首地址的位置,scale表示一个数组元素的大小,i 的偏移量则等于 i * scale + base 但为了优化性能,使用了位移操作,shift 表示scale中 1的位置(scale是2的整数次方)。所以,偏移量的计算变成上面代码中的:i << shift + base,表达的意思就是:
i * scale + base
4 Striped64 与 LongAdder
Striped64 相关的类的继承层次
4.1 LongAdder 原理
AtomicLong 内部是一个volatile long 型变量, 由多个线程对这个变量进行CAS操作。多个线程同时对一个变量进行CAS操作,在高并发的场景下仍不够快,如果再要提高性能,怎么做?
把一个变量拆分成多份,变为多个变量,有些类似于ConcurrentHashMap 的分段锁的例子。把一个Long型拆分成一个base变量外加多个cell,每个Cell 包装成一个Long型变量。当多个线程并发累加的时候,如果并发度低,就直接加到base变量上;如果并发度高,冲突大,平摊到这些Cell 上。最后取值的时候,再把base和这些Cell 求 sum 运算。
4.2 最终一致性
在sum 求和函数中, 并没有对 cells[] 数组加锁。也就是说,一边有线程对其执行求和操作,一边还有线程修改数组里的值,也就是最终一致性,而不是强一致性。
4.3 伪共享于缓存行填充
@sun.misc.Contended
每个CPU都有自己的缓存。缓存与主内存进行数据交换的基本单位叫Cache Line(缓存行)。要刷新到主内存的时候,最少刷新64字节。
4.4 LongAdder核心实现
当一个线程调用add(x) 的时候,首先会尝试使用casBase 把x 加到base变量上。如果不成功,则再用a.case(...) 函数尝试把x加到 Cell 数组的某个元素上。如果还不成功,最后再调用longAccumulate(..)函数
注:Cell[] 数组的大小始终是2的整数次方, 在运行中会不断扩容,每次扩容都长2倍。
4.5 LongAccumulator
LongAccumulator 与 LongAdder 构造函数对比:
public LongAdder(){...} public LongAccumulator(LongBinaryOperator accumulatorFunction,long identity){...} public interface LongBinaryOperator{ long applyAsLong(long left,long right); }
- LongAdder 只能进行累加操作,并且初始值默认为0;LongAccumulator 可以自己定义一个二元操作符,并且传入一个初始值。操作符的左值,就是base变量或者Cells[]中元素的当前值;右值,就是add()函数传入的参数x。
- LongAccumulator 的 accumulate(x) 函数与 LongAdder的 add(x) 函数类似,最后都是调用Striped64的LongAccumulate(...)函数。唯一的差别是LongAdder 的 add(x) 函数调用的是casBase(b,b+x),LongAccumulator 调用的是casBase(b,r),其中,r = function.applyAsLong(b,base,x)。