0%

并发源码-Atomic

AtomicInteger

AtomicInteger,可实现原子化的操作,不需要加锁,他底层是通过CAS实现的,看下使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class AtomicIntegerDemo {

static int i;
static AtomicInteger j = new AtomicInteger();

public static void main(String[] args) {
synchronizedAdd();

atomicAdd();
}


private static void synchronizedAdd() {
for (int k = 0; k < 10; k++) {
new Thread() {
@Override
public void run() {
// 加锁后变慢
synchronized (AtomicIntegerDemo.class) {
System.out.println(++i);
}
}
}.start();
}
}

private static void atomicAdd() {
for (int k = 0; k < 10; k++) {
new Thread() {
@Override
public void run() {
System.out.println(j.incrementAndGet());
}
}.start();
}
}
}

AtomicLong、AtomicBoolean、AtomicReference、LongAdder等类,都是不加锁的,效率比较高,在项目中可以多用下。

CAS

判断此时此刻是否是某个值,如果是,则修改,如果不是则重新查询一个最新的值,再次执行判断,这个操作叫做CAS,Compare and Set。

Atomic原子类底层核心的原理就是CAS,每次尝试修改的时候都先对比一下,有没有人修改过这个值,没有人修改就自己修改,如果有人修改过,就重新查出来最新的值,再次重复那个过程。

Unsafe

Atomic原子类,底层是通过JDK提供的Unsafe类去实现的,这个类不能由用户来实例化的,我们在自己的代码也无法去使用它的方法,他会在源码检查类加载器类型,如果非Bootstrap classloader就会抛异常

1
2
3
4
5
6
7
8
9
10
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 这里有判断,如果类加载器不对,就报错了
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}

然后他具体实现Unsafe封装了一些不安全的操作,例如CAS操作的代码,比较底层,下面开始看下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
try {
// 记录了当前对象里value在内存中的偏移量
// 这个偏移量,是不会发生变化的,后续的CAS操作,会用到这个值,非常关键
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 具体保存int值的变量
private volatile int value;

incrementAndGet源码,是如何完成原子操作的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public final int incrementAndGet() {
// 直接调用了Unsafe的方法,并将自己传入
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

// var1 = AtomicInteger
// var2 = valueOffset
// var4 = 1
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 会用getIntVolatile方法
// 从AtomicInteger对象实例,根据valueOffset偏移量,知道了value这个字段的位置
// 去获取到当前的value的值
var5 = this.getIntVolatile(var1, var2);
// compareAndSwapInt(),CAS方法,是Native的方法
// var5,也就是原始值,假设是1,他会用这个值,和AtomicInteger的value值compare,如果是一样
// 就会set,也就是将value的值给设置为:var5=l(之前拿到的值) + 1(递增的值)

// 如果var5=l(获取到的值),跟AtomicInteger + valueOffset获取到的当前的值,不一样的话
// 此时compareAndSwapInt方法就会返回false,然后while循环就会自动进入下一轮
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
// 如果是cas成功后,还是会返回一个var5=l,这是旧的值,所以incrementAndGet()最后会自己+1再返回
return var5;
}

CAS底层,是通过CPU指令来完成的,对一小块内存进行加锁,保证他的原子性。

Atomic类CAS的三个缺点

ABA问题

比如本来这个值,一开始是A,后来变成了B,然后又变成了A,假设代码期望A就设置新的值,结果线程1A->B->,他将值设置了回去,然后线程2发现确实是A,然后就cas成功了,他无法识别值是否发生过变化。从 Java 1.5 开始,JDK 的 atomic 包里提供了一个类 AtomicStampedReference 来解决 ABA 问题,会比较两个值的引用是否一致,如果一致,才会设置新值。

无限循环问题

上面源码看了,cas在有无限循环在里面的,在高并发场景下,多线程频繁并发修改,可能有的线程就会循环很多次才能cas成功,这个问题在JDK 1.8引入了LongAdder来解决,他采用分段CAS的思路。

多变量原子问题

AtomicInteger,只保证了一个int变量的原子操作,多个变量可以用AtomicReference,将多个变量封装到一个对象里,然后他会检查这个对象的引用是不是一个。

LongAddr

针对无线循环的问题JDK 1.8引入了LongAdder,我们来看一下他是如何提升性能的。

LongAdder里面有一个cell数组,cell是在Striped64中的静态内部类,每个cell维护自己的value,AtomicInteger中仅维护一个全局的value,调用sum将所有cell的value和base相加就是最终的值。

这样就实现了分段CAS,减少并发

1
2
3
4
5
6
7
8
9
10
11
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}

image-20201022193908559