一:为什么需要AtomicInteger原子操作类?
在Java中的运算操作中,我们经常会遇到自增或自减的运算操作,在单线程的场景下,num++解析为num=num+1这样是不会出现问题的,但很多的业务场景是需要在多线程或者并发的场景下进行操作的,若没有进行额外的同步操作,在多线程环境下就是线程不安全的。num++解析为num=num+1,明显,这个操作不具备原子性,多线程并发共享这个变量时必然会出现问题。
下面来个测试代码看看:
public class AtomicIntegerTest {
/*
* @Author :chuxia0811
* @Date: 2021/2/3 23:22
* @Param:同时启动50个线程,每个线程都对同一个变成进行自增1000操作,理论上每个线程自增1000,50个线程自增后变量count应该是50000
* @Return:
* @Description :
*/
private static final int THREADS_CONUT = 50;
public static int count = 0;
public static void increase() {
count++;
}
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_CONUT];
for (int i = 0; i < THREADS_CONUT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
increase();
System.out.println(count);
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
}
}
执行结果如下:
.
.
.
49806
49807
49808
49809
可见最终的结果并不是理想中的50000,而是比实际的少很多。
这里运行了50个线程,每个线程对count变量进行1000此自增操作,如果上面这段代码能够正常并发的话,最后的结果应该是50000才对,但实际结果却发现每次运行的结果都不相同,都是一个小于50000的数字。这是为什么呢?
要是换成volatile修饰count变量呢?
顺便说下volatile关键字很重要的两个特性:
1、保证变量在线程间可见,对volatile变量所有的写操作都能立即反应到其他线程中,换句话说,volatile变量在各个线程中是一致的(得益于java内存模型—“先行发生原则”);
2、禁止指令的重排序优化;
那么换成volatile修饰count变量后:
public class AtomicIntegerTest {
private static final int THREADS_CONUT = 20;
public static volatile int count = 0;
public static void increase() {
count++;
}
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_CONUT];
for (int i = 0; i < THREADS_CONUT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
increase();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
}
运行后结果依旧小于50000,这又是为什么么? 上面的论据是正确的,也就是上面第一条特性内容,但是这个论据并不能得出"基于volatile变量的运算在并发下是安全的"这个结论,因为核心点在于java里的运算(比如自增)并不是原子性的。
把上面的代码改造成AtomicInteger原子类型:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
private static final int THREADS_CONUT = 20;
public static AtomicInteger count = new AtomicInteger(0);
public static void increase() {
count.incrementAndGet();
}
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_CONUT];
for (int i = 0; i < THREADS_CONUT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
increase();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
}
结果每次都输出20000,程序输出了正确的结果,这都归功于AtomicInteger.incrementAndGet()方法的原子性。
所以再返回来看AtomicInteger.incrementAndGet()方法:
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
incrementAndGet()方法在一个无限循环体内,不断尝试将一个比当前值大1的新值赋给自己,如果失败则说明在执行"获取-设置"操作的时已经被其它线程修改过了,于是便再次进入循环下一次操作,直到成功为止。这个便是AtomicInteger原子性的"诀窍"了,继续进源码看它的compareAndSet方法:
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
总结对比:
1.在使用Integer的时候,必须加上synchronized保证不会出现并发线程同时访问的情况,而在AtomicInteger中却不用加上synchronized,在这里AtomicInteger是提供原子操作的。
2.AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。
3.AtomicInteger提供原子操作来进行Integer的使用,因此十分适合高并发情况下的使用。
4.AtomicInteger是在使用非阻塞算法实现并发控制,在一些高并发程序中非常适合,但并不能每一种场景都适合,不同场景要使用使用不同的数值类。