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

 

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