volatile 不保证原子性

什么是原子性,说白了就是整个程序中的所有操作要么都执行,要么都不执行。

但是 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);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章