原⼦操作类AtomicInteger详解
为什么需要AtomicInteger原⼦操作类?
对于Java中的运算操作,例如⾃增或⾃减,若没有进⾏额外的同步操作,在多线程环境下就是线程不安全的。num++解析为
num=num+1,明显,这个操作不具备原⼦性,多线程并发共享这个变量时必然会出现问题。测试代码如下:
public class AtomicIntegerTest {
private static final int THREADS_CONUT = 20;
public static int count = 0;
public static void increa() {
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++) {
increa();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
}
这⾥运⾏了20个线程,每个线程对count变量进⾏1000此⾃增操作,如果上⾯这段代码能够正常并发的话,最后的结果应该是20000才对,但实际结果却发现每次运⾏的结果都不相同,都是⼀个⼩于20000的数字。这是为什么呢?
要是换成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 increa() {
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++) {
increa();
}
}
});
threads[i].start();
}
足球标语
while (Thread.activeCount() > 1) {
Thread.yield();
}
手工怎么做简单又漂亮
System.out.println(count);
}
}
结果似乎⼜失望了,测试结果和上⾯的⼀致,每次都是输出⼩于20000的数字。这⼜是为什么么? 上⾯的论据是正确的,也就是上⾯标红的内容,但是这个论据并不能得出"基于volatile变量的运算在并发下是安全的"这个结论,因为核⼼点在于java⾥的运算(⽐如⾃增)并不是原⼦性的。
⽤了AtomicInteger类后会变成什么样⼦呢?
一个熟悉的人把上⾯的代码改造成AtomicInteger原⼦类型,先看看效果
import urrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
private static final int THREADS_CONUT = 20;
public static AtomicInteger count = new AtomicInteger(0);
public static void increa() {
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++) {
李慕清
increa();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
}
结果每次都输出20000,程序输出了正确的结果,这都归功于AtomicInteger.incrementAndGet()⽅法的原⼦性。
⾮阻塞同步
同步:多线程并发访问共享数据时,保证共享数据再同⼀时刻只被⼀个或⼀些线程使⽤。
我们知道,阻塞同步和⾮阻塞同步都是实现线程安全的两个保障⼿段,⾮阻塞同步对于阻塞同步⽽⾔主要解决了阻塞同步中线程阻塞和唤醒带来的性能问题,那什么叫做⾮阻塞同步呢?在并发环境下,某个线程对共享变量先进⾏操作,如果没有其他线程争⽤共享数据那操作就成功;如果存在数据的争⽤冲突,那就才去补偿措施,⽐如不断的重试机制,直到成功为⽌,因为这种乐观的并发策略不需要把线程挂起,也就把这种同步操作称为⾮阻塞同步(操作和冲突检测具备原⼦性)。在硬件指令集的
发展驱动下,使得 "操作和冲突检测" 这种看起来需要多次操作的⾏为只需要⼀条处理器指令便可以完成,这些指令中就包括⾮常著名的CAS指令(Compare-And-Swap⽐较并交换)。《深⼊理解Java虚拟机第⼆版.周志明》第⼗三章中这样描述关于CAS机制:
图取⾃《深⼊理解Java虚拟机第⼆版.周志明》13.2.2
所以再返回来看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 ts 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. Fal return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
pareAndSwapInt(this, valueOfft, expect, update);
}
可以看到,compareAndSet()调⽤的就是pareAndSwapInt()⽅法,即Unsafe类的CAS操作。
使⽤⽰例如下图,⽤于标识程序执⾏过程中是否发⽣了异常,实现中:
2020-02-12补充如下内容:
原⼦类相⽐于普通的锁,粒度更细、效率更⾼(除了⾼度竞争的情况下)
如果对于上⾯的⽰例代码中使⽤了thread.yield()之类的⽅法不清晰的,可以直接看下⾯的代码压测: