併發編程與高併發解決方案學習(線程安全性-可見性volatile)

一、導致共享變量在線程間不可見的原因

線程交叉執行
重排序結合線程交叉執行
共享變量更新後的值沒有在工作內存與主內存間及時更新
 

二、可見性-Synchronized

線程解鎖前,必須把共享變量的最新值刷新到主內存
線程加鎖時,將清空工作內存中共享變量的值,從而使用共享變量時需要從主內存重新讀取最新值(注意:加鎖與解鎖是同一把鎖)
 

三、可見性-volatile

通過加入內存屏障禁止重排序優化來實現
※對volatile變量寫操作時,會在寫操作後加入一條store屏障指令,將本地內存中的共享乾糧值刷新到主內存
※對volate變量讀操作時,會在讀操作前加入一條load屏障指令,從主內存中讀取共享變量

 

 

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
@NotThreadSafe
public class CountExample4 {

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

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

    public static volatile int count = 0;

    public static void main(String[] args) throws Exception {
        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 (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        count++;
        // 1、count
        // 2、+1
        // 3、count
    }
}

 

四、volatile 不適合這種累加場景。

    您只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:
1.對變量的寫操作不依賴於當前值。
2.該變量沒有包含在具有其他變量的不變式中。
    實際上,這些條件表明,可以被寫入 volatile 變量的這些有效值獨立於任何程序的狀態,包括變量的當前狀態。
第一個條件的限制使 volatile 變量不能用作線程安全計數器。雖然增量操作(x++)看上去類似一個單獨操作,實際上它是一個由讀取-修改-寫入操作序列組成的組合操作,必須以原子方式執行,而 volatile 不能提供必須的原子特性。實現正確的操作需要使 x 的值在操作期間保持不變,而 volatile 變量無法實現這點。(然而,如果將值調整爲只從單個線程寫入,那麼可以忽略第一個條件。)

    大多數編程情形都會與這兩個條件的其中之一衝突,使得 volatile 變量不能像 synchronized 那樣普遍適用於實現線程安全。
 
volatile boolean inited = false;
//線程1
context = loadContext();
inited = true;
//線程2
while(!inited){
sleep();
}
doSomethingWithConfig(context)

 

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