JUC包(java.util.concurrent)提供了一系列原子性操作类,这些类都是使用非阻塞算法CAS实现的,相比使用锁实现原子性操作在性能上有很大提高.
1. 原子变量操作类
JUC并发包包含有AtomicInteger,AtomicLong,AtomicBoolean等原子性操作类,原理都是CAS算法.
一下都是以AtomicLong类为例.
(1). 递增和递减操作
// 自增,然后获取值
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
// 自减,然后获取值
public final long decrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
}
// 先获取值,然后自增
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
// 先获取值,然后自减
public final long getAndDecrement() {
return unsafe.getAndAddLong(this, valueOffset, -1L);
}
这些方法内部都是通过Unsafe的getAndAddLong方法来实现操作,原操作返回的都是修改之前的值.
(2). boolean compareAndSet(long expect,long update)方法
直接调用了unsafe.compareAndSwapLong()方法.
如果变量的值等于expect,替换为update并返回true,否则返回flase.
2. JDK 8 新增的原子操作类LongAdder
(1). 简单介绍
使用之前的AtomicLong是,高并发情况下,由于CAS操作的缺陷,会导致大量线程同时竞争更新一个原子变量,会有大量线程竞争失败后无限循环进行自旋尝试.
LongAdder类中将一个变量分解为多个变量,让每个线程都可以有线程可以操作,解决了性能问题.
在LongAdder类中,内部维护了多个Cell变量和一个base值.每一个Cell里都有一个初始值为0的long型变量,多个线程竞争同一个Cell原子变量使如果失败了,不会一直自旋式的重试,而是试图在其他Cell上进行CAS尝试.
Cell占用的内存相对较大,在使用时才会创建,也就是惰性加载.
一开始所有的累加操作都是对base变量进行的,当并发大了之后,初始化Cell数组,并保存Cell数量为2^n(初次为2).
原子性数组的内存是连续的,所以要使用@sun.misc.Contended注解对Cell类记性字节填充.
(2). LongAdder代码分析
LongAdder类继承自Striped64类,其内部维护着三个变量:base,cells[]和cellsBusy.前两个变量用来记性计数,第三个变量用来实现自旋锁,只有0和1两个值.
Cell的构造很简单,内部维护一个被声明为volatile的变量(保证可见性),通过CAS操作进行更新(保证原子性).
1). long sum()
返回当前的计数和.返回的是调用时的快照,多线程情况下是可能对其改变的.所以LongAdder并不能完全代替LongAtomic.
2). void reset()
重置操作,将base置为零,如果Cell数组有元素,全部重置为0.
3). long sumThenReset
sum的改造版本,返回计数和,并重置.
多线程下会有问题,一个线程将值置为0,那么其他线程就是在0上面进行计数了.
4). long longValue()
等价于sum
5). void add(long x)
如果Cell数组为空,则在base上进行计数,如果Cell不为空或者CAS操作失败了,获取当前线程应该访问的cells数组中的Cell元素(使用 getProbe()&(cells.length-1) 计算其下标).如果cells的元素个数小于当前机器CPU的个数,并且当前出现了CAS错误,就会进行cells数组的扩容.
getProbe()用于获取当前线程中变量threadLocalRandomProbe的值,可以看作是线程的hash值,CAS失败线程会重新计算这个值,以减少下次冲突的可能性.
6). void longAccumulate(...)
这个方法用来进行cells数组的初始化和扩容.
上面提到了如果cells的元素个数小于当前机器CPU的个数,并且当前出现了CAS错误,就会进行cells数组的扩容.
在进行扩容或者初始化时,会先将cellsBusy设置为1,当当前线程开始进行方法内部逻辑时,别的线程不能进行扩容.不使用CAS,因为cellsBusy变量是声明为volatile类型的.
扩容时进行的操作是建立一个2被于原来容量的数组,将原数组的值复制进去.
3. LongAccumulator类原理探究
LongAdder类是LongAccumulator类的一个特例,后者比前者功能更强大.可以提供非0的初始值,可以自定义计数规则.