[Java并发]-----第4章 Java并发包中原子操作类原理剖析

​ 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尝试.

image-20200221124501592
image-20200221124521654

​ 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的初始值,可以自定义计数规则.

你可能感兴趣的:([Java并发]-----第4章 Java并发包中原子操作类原理剖析)