原子类 AtomicInteger

原子类 AtomicInteger

薛定谔的汪

前言

当多线程更新某个共享变量时,可能会出现非预期结果,比如i++这样的操作,解决的办法有使用synchronized 或者 Lock 锁,但这样做其实让多线程不会同时更新这个变量,虽然线程安全了,但存在效率、性能问题。

jdk1.5提供了一系列原子操作类,它们存在于java.util.concurrent.atomic包下,这些原子类具有简单、性能高效、线程安全的优点。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class IntTest implements Runnable {

private static int num;

private static CountDownLatch countDownLatch = new CountDownLatch(1000);

@Override
public void run() {
num++;
countDownLatch.countDown();
}

public static void main(String[] args) throws InterruptedException {
IntTest atomicIntegerTest = new IntTest();
for (int i = 0; i < 1000; i++) {
new Thread(atomicIntegerTest).start();
}
//等待1000个线程都执行完
countDownLatch.await();
System.out.println(num);
}
}

预期打印1000,但多次运行,总得到小于1000的值,这是因为成员变量num ,存在线程安全问题。

修改为用 AtomicInteger:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AtomicIntegerTest implements Runnable {

//初始值0
private static AtomicInteger num = new AtomicInteger(0);

private static CountDownLatch countDownLatch = new CountDownLatch(1000);

@Override
public void run() {
num.getAndIncrement();
countDownLatch.countDown();
}

public static void main(String[] args) throws InterruptedException {
AtomicIntegerTest atomicIntegerTest = new AtomicIntegerTest();
for (int i = 0; i < 1000; i++) {
new Thread(atomicIntegerTest).start();
}
//等待1000个线程都执行完
countDownLatch.await();
System.out.println(num);
}
}

反复运行得到的结果都是1000,保证了原子性。

AtomicInteger 常用方法

int get()

获取 AtomicInteger 的值value

void lazySet(int newValue)

最终会设置成newValue,但是可能导致其他线程在一段时间内仍可以读到旧的值。

int getAndSet(int newValue)

以原子的方式设置为newValue,并返回旧值

boolean compareAndSet(int expect, int update)

如果输入的值等于预期值,则以原子的方式更新为新的值update

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做

int getAndIncrement()

返回旧值,自增1

int getAndDecrement()

返回旧值,自减1

int getAndAdd(int delta)

返回旧值,自增步长 delta

int incrementAndGet()

自增1,返回新的值

AtomicInteger 原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14

private volatile int value;

public AtomicInteger(int initialValue) {
value = initialValue;
}

public AtomicInteger() {
}

public final int get() {
return value;
}
......

属性 value 是 AtomicInteger 包装类真正的值,在初始化时可以指定其初始值,默认为0;

value用 volatile 修饰,我们知道 volatile可以保证可见性,但不能保证原子性,因为i++这种不是原子操作。

以方法getAndIncrement()为例:

1
2
3
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}

查看unsafe.getAndAddInt(…)源码:

1
2
3
4
5
6
7
8
9
10
11
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
//自旋
do {
//获取旧值
var5 = this.getIntVolatile(var1, var2);
//CAS
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;
}

getIntVolatile(var1, var2)compareAndSwapInt(var1, var2, var5, var5 + var4)是 native 方法,调用 C 语言。

getAndAddInt(...)方法中,通过不断自旋CAS 的方式去更新。

AtomicInteger的其他原子更新方法也都是基于此。

自旋+CAS 的缺点

并发情况下,对于更新变量,竞争锁的线程较多的时候,每个线程都要去进行自旋,很消耗 CPU。

LongAdder

jkd8引入 LongAdder,在线程较多的并发情况下,它的性能要比Atomic*使用自旋+CAS 的方式要高,其核心原理是在自旋+CAS 的基础上,引用了锁分段技术。

LongAdder 比 AtomicLong 更高效,缺点是如果有并发更新,可能导致最终的数据有误差。

更适合在高并发下、没有并发更新时进行统计的需求。

  • Title: 原子类 AtomicInteger
  • Author: 薛定谔的汪
  • Created at : 2018-08-23 18:01:54
  • Updated at : 2023-11-17 19:37:37
  • Link: https://www.zhengyk.cn/2018/08/23/java/AtomicInteger/
  • License: This work is licensed under CC BY-NC-SA 4.0.