共享變量在線程中的可見性問題分析

導致共享變量在線程間不可見的原因:
1)線程交叉執行
2)重排序結合線程交叉執行
3)共享變量更新後的值沒有在工作內存與主存間及時更新
 
可見性-synchronized
JVM中關於synchronized的兩條規定:
1)線程解鎖前,必須把共享變量的最新值刷到主內存
2)線程加鎖時,將清空工作內存中共享變量的值,從而使用共享變量時需要從主內存中重新讀取最新的值(注意:加鎖和解鎖是同一把鎖
可見性-volatile
通過加入內存屏障禁止重排序優化來實現(對於被volatile變量的操作都是直接針對主內存)
1)對volatile變量寫操作時,會在寫操作後加入一條store屏障指令,將本地內存中的共享變量值刷新到主內存

2)對volatile變量讀操作時,會在讀操作前加入一條load屏障指令,從主內存中讀取共享變量

 

問題:只利用volatile來修飾計數器,是否能夠保證計數器是原子操作的了?答案是不能的。
比如如下代碼:(在count前加入volatile修飾)

 

public class AtomicExample7 {

    //  請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    public static volatile int count = 0;

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i< clientTotal; i ++) {
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);

    }

    private static void add() {
        count ++;
    }
}

 

輸出:
[main] INFO com.example.concurrent.example.count.AtomicExample7 - count:4977
分析:
在上面的代碼中的計數變量count前加入了volatile保證變量count的讀和寫能夠及時的更新到內存中,但是運行出來的結果仍然是非線程安全的,原因是在cout++的操作,可以分爲三步:
1、取得count的值; 2、對count進行加1操作; 3、寫count的值到主內存中;
上面這三步合起來就不是線程安全的,比如,兩個線程可能同時取得count的值,然後,同時進行加1操作,並寫回主存,這樣就丟掉了一次加1的操作。
即:voliatile不適用於計數的場景。

 

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