详解 AtomicInteger 原理

在并发场景中,当多线程需要对同一份资源做操作时,就会产生线程安全问题。以最简单的 int i++ 为例,i++并不是原子操作,编译出来后分为三步:1,获取值;2,修改值;3,设置值。如果有多线程执行i++,则通常不会得到正确的结果。

下面这段代码执行结果就说明了上述问题:

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
38
39
40
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Test {

static Logger logger = LoggerFactory.getLogger(Test.class);

private static int n = 0;

public static void main(String[] args) throws Exception {
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
n++;
try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { }
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
n++;
try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { }
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();

logger.info("n = {}", n);
}
}

// 结果打印:
11:17:05.185 [main] INFO Test - n = 1970
// 并不是想要的 2000

为了解决这类问题,JDK 并发包里提供了很多线程安全的类。如:int对应线程安全的AtomicInteger。类似的还有:AtomicBooleanAtomicLongAtomicReference

使用 AtomicInteger 实现上面的代码,每次得到的都是 2000。

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
38
39
40
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Test {

static Logger logger = LoggerFactory.getLogger(Test.class);

private static AtomicInteger n = new AtomicInteger(0);

public static void main(String[] args) throws Exception {
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
n.incrementAndGet();
try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { }
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
n.incrementAndGet();
try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { }
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();

logger.info("n = {}", n);
}
}
// 结果打印:
11:32:52.102 [main] INFO Test - n = 2000

AtomicInteger提供了如下常用方法,与之对应自增自减操作。

1
2
3
4
incrementAndGet(); // ++i
getAndIncrement(); // i++
decrementAndGet(); // --i
getAndDecrement(); // i--

那么它内部是如何实现线程安全的呢?

1,其内部存储了 volatile 修饰的 value 作为具体值,volatile避免指令重排,保证了该 value 的多线程可见性,即修改时其它线程能获取到最新值。

1
private volatile int value;

2,以 incrementAndGet() 方法为例,内部调用了的 Unsafe 类的 getAndAddInt 方法,getIntVolatilecompareAndSwapInt 都是都是 native 方法。

1
2
3
4
5
6
7
8
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt) {
int i;
do {
i = getIntVolatile(paramObject, paramLong); // 获取主存的值
} while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
// CAS 操作,从内存中取出偏移量,比较数据与期望值一致,并修改新值。
return i;
}

3,CAS 是乐观锁的一种实现方式,利用 CPU 自身特性保证原子性,避免类似悲观锁的线程切换,性能较强。