AtomicInteger-用法

前言

AtomicInteger 源自 java.util.concurrent.atomic 包,另外有 AtomicBoolean、AtomicInteger、AtomicLong、AtomicLongArray、AtomicReference 等原子类,主要用于在高并发环境下,简化同步处理.

Atomic 包介绍

Java1.5 的 Atomic 包名为 java.util.concurrent.atomic。这个包提供了一系列原子类。这些类可以保证多线程环境下,当某个线程在执行 atomic 的方法时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由 JVM 从等待队列中选择一个线程执行。Atomic 类在软件层面上是非阻塞的,它的原子性其实是在硬件层面上借助相关的指令来保证的。

Atomic 包中的类可以分成 4 组:

  • AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

  • AtomicIntegerArray,AtomicLongArray

  • AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

  • AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray

我们来看一下最简单的 AtomicInteger 有哪些常见的方法以及这些方法的作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
get()             直接返回值

getAndAdd(int) 增加指定的数据,返回变化前的数据

getAndDecrement() 减少1,返回减少前的数据

getAndIncrement() 增加1,返回增加前的数据

getAndSet(int) 设置指定的数据,返回设置前的数据

addAndGet(int) 增加指定的数据后返回增加后的数据

decrementAndGet() 减少1,返回减少后的值

incrementAndGet() 增加1,返回增加后的值

lazySet(int) 仅仅当get时才会set

compareAndSet(int, int) 尝试新增后对比,若增加成功则返回true否则返回false

在 Java 中,++i 和 i++ 操作并不是线程安全的,在使用的时候,不可避免的会用到 synchronized 关键字。而 AtomicInteger 则通过一种线程安全的加减操作接口。

AtomicInteger 的基本方法

创建一个 AtomicInteger

1
System.out.println(atomicInteger.get());

输出 : 123

创建一个不传值的,默认值为 0

1
2
AtomicInteger atomicInteger = new AtomicInteger();
System.out.println(atomicInteger.get());

输出: 0

获取和赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
atomicInteger.get(); //获取当前值
atomicInteger.set(999); //设置当前值

atomicInteger.compareAndSet(expectedValue,newValue)
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
System.out.println(atomicInteger.get());

int expectedValue = 123;
int newValue = 234;
Boolean b =atomicInteger.compareAndSet(expectedValue, newValue);
System.out.println(b);
System.out.println(atomicInteger);
}

输出结果为: 0 false 0

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(123);
System.out.println(atomicInteger.get());

int expectedValue = 123;
int newValue = 234;
Boolean b =atomicInteger.compareAndSet(expectedValue, newValue);
System.out.println(b);
System.out.println(atomicInteger);

}

输出结果为: 123 true 234

由上可知该方法表示,atomicInteger 的值与 expectedValue 相比较,如果不相等,则返回 false,atomicInteger 原有值保持不变;如果两者相等,则返回 true,atomicInteger 的值更新为 newValue

getAndAdd()方法与 AddAndGet 方法

1
2
3
4
5
6
7
8
AtomicInteger atomicInteger = new AtomicInteger(123);
System.out.println(atomicInteger.get()); --123

System.out.println(atomicInteger.getAndAdd(10)); --123 获取当前值,并加10
System.out.println(atomicInteger.get()); --133

System.out.println(atomicInteger.addAndGet(10)); --143 获取加10后的值,先加10
System.out.println(atomicInteger.get()); --143

getAndDecrement()和 DecrementAndGet()方法

1
2
3
4
5
6
7
8
9
AtomicInteger atomicInteger = new AtomicInteger(123);
System.out.println(atomicInteger.get()); --123

System.out.println(atomicInteger.getAndDecrement()); --123 获取当前值并自减
System.out.println(atomicInteger.get()); --122


System.out.println(atomicInteger.decrementAndGet()); --121 先自减再获取减1后的值
System.out.println(atomicInteger.get()); --121

使用 AtomicInteger,即使不用同步块 synchronized,最后的结果也是 100,可用看出 AtomicInteger 的作用,用原子方式更新的 int 值。主要用于在高并发环境下的高效程序处理。使用非阻塞算法来实现并发控制。

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 Counter {

public static AtomicInteger count = new AtomicInteger(0);

public static void inc(){
try{
Thread.sleep(1); //延迟1毫秒

}catch (InterruptedException e){ //catch住中断异常,防止程序中断
e.printStackTrace();

}
count.getAndIncrement();//count值自加1
}


public static void main(String[] args) throws InterruptedException {


final CountDownLatch latch = new CountDownLatch(100);

for(int i=0;i<100;i++){
new Thread(new Runnable() {
@Override
public void run() {
Counter.inc();
latch.countDown();
}
}).start();
}
latch.await();

System.out.println("运行结果:"+Counter.count);


}
}

运行结果: 100

使用普通 Integer

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
public class Counter {

public volatile static int count = 0;

public static void inc(){
try{
Thread.sleep(1); //延迟1毫秒

}catch (InterruptedException e){ //catch住中断异常,防止程序中断
e.printStackTrace();

}
count++;//count值自加1
}


public static void main(String[] args) throws InterruptedException {


final CountDownLatch latch = new CountDownLatch(100);

for(int i=0;i<100;i++){
new Thread(new Runnable() {
@Override
public void run() {
Counter.inc();
latch.countDown();
}
}).start();
}
latch.await();

System.out.println("运行结果:"+Counter.count);
}
}

运行结果:98

如果在 inc 方法前面加个 synchronized 也能是线程安全的;

它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

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
41
42
import java.util.concurrent.CountDownLatch;

/**
* created by guanguan on 2017/10/23
**/
public class Counter {

public volatile static Integer count = 0;

public synchronized static void inc(){
try{
Thread.sleep(1); //延迟1毫秒

}catch (InterruptedException e){ //catch住中断异常,防止程序中断
e.printStackTrace();

}
count++;//count值自加1
}


public static void main(String[] args) throws InterruptedException {


final CountDownLatch latch = new CountDownLatch(100);

for(int i=0;i<100;i++){
new Thread(new Runnable() {
@Override
public void run() {
Counter.inc();
latch.countDown();
}
}).start();
}
latch.await();

System.out.println("运行结果:"+Counter.count);


}
}

运行结果:100

synchronized 的使用说明:

一、当两个并发线程访问同一个对象 object 中的这个 synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问 object 的一个 synchronized(this)同步代码块时,另一个线程仍然可以访问该 object 中的非 synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问 object 的一个 synchronized(this)同步代码块时,其他线程对 object 中所有其它 synchronized(this)同步代码块的访问将被阻塞。

四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问 object 的一个 synchronized(this)同步代码块时,它就获得了这个 object 的对象锁。结果,其它线程对该 object 对象所有同步代码部分的访问都被暂时阻塞。

五、以上规则对其它对象锁同样适用.

6、从上面的例子中我们可以看出:使用 AtomicInteger 是非常的安全的.而且因为 AtomicInteger 由硬件提供原子操作指令实现的。在非激烈竞争的情况下,开销更小,速度更快。

java 的关键域有 3 个

1
2
3
4
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;

这里, unsafe 是 java 提供的获得对对象内存地址访问的类,注释已经清楚的写出了,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是 AtomicInteger 中的一个工具。

valueOffset 是用来记录 value 本身在内存的便宜地址的,这个记录,也主要是为了在更新操作在内存中找到 value 的位置,方便比较。

注意:value 是用来存储整数的时间变量,这里被声明为 volatile,就是为了保证在更新操作时,当前线程可以拿到 value 最新的值(并发环境下,value 可能已经被其他线程更新了)。

这里,我们以自增的代码为例,可以看到这个并发控制的核心算法:

源码

1
2
3
4
5
6
7
8
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}

AtomicInteger 源码分析

1
2
3
4
5
6
7
8
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}

方法中采用了 CAS 操作,每次从内存中读取数据然后将此数据和+1 后的结果进行 CAS 操作,如果成功就返回结果,否则重试直到成功为止。而 compareAndSet 利用 JNI 来完成 CPU 指令的操作。

1
2
3
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

整体的过程就是这样子的,利用 CPU 的 CAS 指令,同时借助 JNI 来完成 Java 的非阻塞算法。

什么是 CAS

CAS,Compare and Swap 即比较并交换。 java.util.concurrent 包借助 CAS 实现了区别于 synchronized 同步锁的一种乐观锁。乐观锁就是每次去取数据的时候都乐观的认为数据不会被修改,所以不会上锁,但是在更新的时候会判断一下在此期间数据有没有更新。CAS 有 3 个操作数:内存值 V,旧的预期值 A,要修改的新值 B。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做。CAS 的关键点在于,系统在硬件层面保证了比较并交换操作的原子性,处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。

CAS 的优缺点

CAS 由于是在硬件层面保证的原子性,不会锁住当前线程,它的效率是很高的。

CAS 虽然很高效的实现了原子操作,但是它依然存在三个问题。

1、ABA 问题。CAS 在操作值的时候检查值是否已经变化,没有变化的情况下才会进行更新。但是如果一个值原来是 A,变成 B,又变成 A,那么 CAS 进行检查时会认为这个值没有变化,但是实际上却变化了。ABA 问题的解决方法是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么 A-B-A 就变成 1A-2B-3A。从 Java1.5 开始 JDK 的 atomic 包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。

2、并发越高,失败的次数会越多,CAS 如果长时间不成功,会极大的增加 CPU 的开销。因此 CAS 不适合竞争十分频繁的场景。

3、只能保证一个共享变量的原子操作。当对多个共享变量操作时,CAS 就无法保证操作的原子性,这时就可以用锁,或者把多个共享变量合并成一个共享变量来操作。比如有两个共享变量 i = 2,j=a,合并一下 ij=2a,然后用 CAS 来操作 ij。从 Java1.5 开始 JDK 提供了 AtomicReference 类来保证引用对象的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作。