什么是原子性,说白了就是整个程序中的所有操作要么都执行,要么都不执行。
但是 volatile 可以保证可见性,但是不能保证原子性,所以是一个轻量级的同步机制。
例如:下面代码加了volatile,但是不能保证原子性,number的最终结果不是20000.
class Data {
public volatile int number;
public void add(){
number++;
}
}
public class Main {
public static void main(String[] args) {
Data data = new Data();
for (int i=0; i<10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i<2000; i++) {
data.add();
}
}
}, String.valueOf(i)).start();
}
while (Thread.activeCount() >=2) {
Thread.yield();
}
System.out.println(data.number);
}
}
为什么volatile不能保证原子性?
首先,number++这个操作不能保证原子性,numberi从底层来看被分为三部分:将主物理内存中number的值拷贝到线程的栈内存中 ---》 线程在自身的栈内存中进行number+1操作 ---》 线程将新的number的值写回主物理内存。
然后,在执行的时候,假设线程A、B、C在某一时刻都哪去到了number = 0的值,然后都在自身的栈内存中进行了 number+1的操作,然后线程B先写回主物理内存,此时,线程 A、C被挂起,然后线程B写结束之后,通知其他线程该number的值被修改了,但是!其他线程(A、C)被唤醒的速度太快,没来的及接受通知就已经将A得到number=1再一次写入主物理内存,造成了丢失写值的操作。所以最后 data.number一般都会小于20000。
怎么解决?
1、使用synchronized关键字,但是有个问题就是synchronized太“重”了,所以不是最优的解决方案。
2、使用 java.util.concurrent.atomic 这个包,此案例中使用 AtomicInteger 就可以保证原子性。
修改如下:
class Data {
public AtomicInteger atonumber = new AtomicInteger();
public void atoAdd() {
atonumber.getAndIncrement();
}
}
public class Main {
public static void main(String[] args) {
Data data = new Data();
for (int i=0; i<10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i<2000; i++) {
data.atoAdd();
}
}
}, String.valueOf(i)).start();
}
while (Thread.activeCount() >=2) {
Thread.yield();
}
System.out.println(data.atonumber);
}
}