上一篇讲了volatile关键字 volatile关键字只能保证可见性,并不具有原子性,它不是原子操作。
什么是原子性?你可以理解为,在运行过程中,不可能因为cpu调度机制被中间突然打断,我一定是直接串行完成的。原子操作可能是一个步骤,也可能是一些列步骤的集合。该操作顺序不会改变,也不能被切割成某一步骤单独执行。要么一起按顺序执行,要么都不执行,这根事物极其类似,事物概念中的ACID特性中的A指的就是原子性。
下面来演示一个volatile非原子性的小例子。
import java.util.ArrayList;
import java.util.List;
public class o1volatilefeiyuanzixing {
public static void main(String [] args){
m3 a = new m3();
List<Thread> list = new ArrayList<Thread>();
for(int i=0; i<5; i++){
list.add(new Thread(a::add));
}
list.forEach(b -> b.start());
for(int i=0; i<5; i++){
try {
list.get(i).join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(a.count);
}
}
class m3{
volatile int count = 0;
void add(){
for(int i=0; i<1000; i++){
this.count++;
}
}
}
这段代码启动五个线程每个线程执行一次add方法,add方法把count加1000次,那么五次全部执行完最终结果应该是5000,我们来看看结果。
并且你每次计算的结果都不一致,这就是因为volatile没有原子性,导致我们再执行过程中被其他线程打断干扰导致count的值出现覆盖导致的。
JAVA开发人员为了解决这个问题专门给我们提供了Atomic一系列的原子操作类。
这里我们把count++替换成
//原子类
AtomicInteger count = new AtomicInteger();
void add(){
for(int i=0; i<1000; i++){
count.incrementAndGet(); //替换count++,该操作是原子操作,你可以把它看成是加了synchronized,但是效率高很多
}
}
那么我们接着运行一下结果,绝对是5000。因为count.incrementAndGet();是不可分的,一个正在运行该方法的线程不可能被其他线程打断。