1.AtomicInteger介绍
AtomicInteger是一个提供原子操作的Integer类,通过线程安全的非阻塞的方式操作加减;在以往java语言中,i++、i--、++i、--i都是线程不安全的,都是先加/减然后在赋值的方式,独立使用并不适合高并发的场景,若想在高并发使用,必须加阻塞关键字synchronized,而这样会对并发性能影响比较大;
2.AtomicInteger简单使用
具体代码如下
package com.example.demo.concurrent;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
static class MyThread implements Runnable{
AtomicInteger ato = new AtomicInteger();
int val = 0;
@Override
public void run() {
for(int i=0;i<10000;i++){
ato.getAndIncrement();
}
for(int i=0;i<10000;i++){
val++;
}
}
}
public static void main(String[] agrs) throws InterruptedException{
AtomicIntegerTest.MyThread myThread = new AtomicIntegerTest.MyThread();
Thread thread = new Thread(myThread);
Thread thread2 = new Thread(myThread);
thread.start();
thread2.start();
Thread.sleep(500);
System.out.println("AtomicInteger value="+myThread.ato.get()+",val++ value="+myThread.val);
}
}
运行结果:AtomicInteger value=20000,val++ value=11256,明显使用val++因并发导致异常数据,而使用AtomicInteger正常
public void run() {
for(int i=0;i<10000;i++){
ato.getAndIncrement();
}
for(int i=0;i<10000;i++){
synchronized(this){ //加同步
val++;
}
}
}
加同步之后两者结果是一样的 AtomicInteger value=20000,val++ value=20000(后面可以测试下运行性能)
3.AtomicInteger原理说明
先看下一段AtomicInteger的源码
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
从头部代码可看出使用了Unsafe,Unsafe是JDK内部的工具类,其中Unsafe.compareAndSwapInt实现cas安全的自增
定义了全局字段valueOffset,是保存在内存中的偏移量,保存的是当前AtomicInteger的值,并在static静态块中初始化。
AtomicInteger的真正的值value是一个volatile的类型的变量,volatile是保存在主内存的,是一个所有线程共享可见的变量,不是保存在寄存器(只有拥有线程可见),也就是当某个线程改变volatile修饰符修饰的变量,其他线程都能获得最新的值。
现在再来看看实例中使用的方法
AtomicInteger中有很多方法,例如getAndIncrement()
相当于i++
和getAndAdd()
相当于i+=n
。从源码中我们可以看出这几种方法的实现很相似,所以我们主要分析getAndIncrement()
方法的源码。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
* Atomically decrements by one the current value.
*
* @return the previous value
*/
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//Unsafe类中的代码
public final int getAndAddInt(Object arg0, long arg1, int arg3) {
int arg4;
do {
arg4 = this.getIntVolatile(arg0, arg1);
} while (!this.compareAndSwapInt(arg0, arg1, arg4, arg4 + arg3));
return arg4;
}
getAndIncrement()
方法实现了自增的操作。核心实现是先获取当前值和目标值(也就是value+1),如果compareAndSwapInt(...)
返回成功则该方法返回目标值。那么compareAndSwapInt
是做什么的呢?理解这个方法我们需要
CAS操作理解。
我们学过独占锁和乐观锁的概念。独占锁就是线程获取锁后其他的线程都需要挂起,直到持有独占锁的线程释放锁;乐观锁是先假定没有冲突直接进行操作,如果因为有冲突而失败就重试,直到操作成功。其中乐观锁用到的机制就是CAS,Compare and Swap。另外独占锁典型的操作是数据库for update(synchronized也是独占锁),乐观锁是数据加版本号的方式;
AtomicInteger 中的CAS操作就是compareAndSwapInt()
,其作用是每次从内存中根据内存偏移量(valueOffset
)取出数据,将取出的值跟期望的值expect 比较,如果数据一致就把内存中的值改为update。
这样使用CAS就保证了原子操作。其余几个方法的原理跟这个相同,在此不再过多的解释。
总结一下,AtomicInteger 中主要实现了整型的原子操作,防止并发情况下出现异常结果,其内部主要依靠JDK 中的unsafe 类操作内存中的数据来实现的。volatile 修饰符保证了value在内存中其他线程可以看到其值得改变。CAS操作保证了AtomicInteger 可以安全的修改value 的值。