線程安全底層原理解析

1 什么是可見性?

  • 通過 volatile 修飾的變量被a線程修改b線程能立即讀取到修改后的值,不會出現(xiàn)'臟讀'

2 可見性原理

  • volatile修飾后hsdis多了個Lock匯編指令,Lock匯編指令是一種控制指令,作用是在多線程環(huán)境中,可以基于總線鎖緩存鎖的機(jī)制來達(dá)到共享變量在線程間的可見性

3 硬件層面

  • CPU>內(nèi)存>IO 硬件方面存在很大的處理速度的差異,木桶原理最---最短板決定整體性能
  • 所以硬件方面的性能優(yōu)化要從兩方面著手:
    ①提高短板(基本不可實(shí)現(xiàn))
    ②最大化利用【性能過剩組件(CPU)】

3.1 最大化利用CPU方法

image.png
  • CPU增加高速緩存,cpu絕大多數(shù)的業(yè)務(wù)處理中都會依賴內(nèi)存或者IO進(jìn)行運(yùn)算或數(shù)據(jù)存儲
  • CPU告訴緩存通過降低內(nèi)存/IO讀取頻率來實(shí)現(xiàn)提高整體處理性能
  • CPU高速緩存分為:L1>L2>L3三種,性能依次下降
image.png
  • L1d:L1數(shù)據(jù)緩存
  • L1i:L1指令緩存
  • CPU高速緩存提高了CPU處理過程中頻繁與主內(nèi)存交互的性能
  • CPU高速緩存也帶來了緩存(數(shù)據(jù))一致性的問題

3.2 緩存(數(shù)據(jù))一致性解決方案:

  • 總線鎖
    通過在總線添加鎖的方式來保證緩存(數(shù)據(jù))一致性,當(dāng)cup0通過總線操作數(shù)據(jù)時,其它c(diǎn)pu1將無法獲取總線的使用權(quán)限,對性能影響很大
  • 緩存鎖
    相對于總線鎖緩存鎖的范圍更加精確,降低看控制粒度,通過緩存一致性協(xié)議實(shí)現(xiàn)
  • 緩存一致性協(xié)議MESI
    不同的CPU架構(gòu)里緩存一致性協(xié)議有著各自不同的實(shí)現(xiàn)方式,X86架構(gòu)中是基于MESI協(xié)議
image.png
image.png

image.png

M>Modified 修改狀態(tài)
E>Exclusive 獨(dú)享狀態(tài)
S>Shared 共享狀態(tài):表示數(shù)據(jù)可被多個緩存對象進(jìn)行緩存,且數(shù)據(jù)值與主內(nèi)存一致
I>Invlid 失效狀態(tài)
失效狀態(tài)緩存不可被使用,將從主內(nèi)存中進(jìn)行讀取

3.3 MESI的局限性

  • 當(dāng)某個CPU修改緩存中的數(shù)據(jù)時,首先通知其cup緩存中的相同數(shù)據(jù),其它相同緩存置為失效
    其它CPU緩存失效完成后再通知要修改的CPU,該過程中CUP處于阻塞中,浪費(fèi)了CPU性能


    image.png

3.4 EMSI 改進(jìn)

  • 為了減少緩存被修改過程中的阻塞時長,通知修改時采用異步操作,不進(jìn)行阻塞
    將修改請求緩存到storebuffer中


    image.png
  • storebuffer帶來的問題
value =3;
void cup0{
  value = 10;// 通過storebuffer異步通知其他cpu緩存,將緩存value變?yōu)镮:失效狀態(tài)
  isFinish = true; //E 獨(dú)占狀態(tài)
}

void cup1{
    //由于cup0中storebuffer是異步操作
    //所以理論上村 isFinish=true 而 value=3 這種情況
    if(isFinish){//true 
        assert value == 10;//false
    }
}

storebuffer可能會導(dǎo)致cup的亂序執(zhí)行既"指令重排序",重排序?qū)砜梢娦詥栴}

  • 硬件層面的優(yōu)化,總是會帶來其他問題,無法真正解決可見性問題,所以cpu層面提供指令--內(nèi)存屏障供軟件方面調(diào)用

3.5 內(nèi)存屏障

value =3;
void cup0{
  value = 10;// 通過storebuffer異步通知其他cpu緩存,將緩存value變?yōu)镮:失效狀態(tài)
  加入內(nèi)存屏障
  isFinish = true; //E 獨(dú)占狀態(tài)
}

void cup1{
    //由于cup0中storebuffer是異步操作
    //所以理論上村 isFinish=true 而 value=3 這種情況
    if(isFinish){//true 
        讀取內(nèi)存屏障//由于cup0在  'sFinish = true; //E 獨(dú)占狀態(tài)' 前加入內(nèi)存屏障
                              //所以下面代碼中value值將,直接從主內(nèi)存中進(jìn)行獲取  
        assert value == 10;//false
    }
}
  • cup層面提供了3中內(nèi)存屏障
    讀屏障 store barrier
    寫屏障 load barrier
    全屏障 full barrier

  • X86架構(gòu)中volatile關(guān)鍵字的實(shí)現(xiàn)依賴:volatile--->Lock指令(緩存鎖)--->內(nèi)存屏障

  • 內(nèi)存屏障/指令重排序 等和平臺一級硬件有關(guān),不同硬件是不同的實(shí)現(xiàn).java是跨平臺語言,不需要在業(yè)務(wù)點(diǎn)中考慮硬件的差異性的是依托于JMM內(nèi)存模型的存在

4 JMM虛擬內(nèi)存模型

image.png
  • 語言基本的抽象內(nèi)存模型,本與cpu內(nèi)存模型相類似
  • 線程通過操作工作內(nèi)存來修改數(shù)據(jù),工作內(nèi)存負(fù)責(zé)和主內(nèi)存進(jìn)行通信和數(shù)據(jù)同步
  • JMM虛擬內(nèi)存模型為作為一種標(biāo)準(zhǔn),不同的硬件設(shè)備有著各自的實(shí)現(xiàn)(指令).通過JMM業(yè)務(wù)代碼開發(fā)人員不需要關(guān)系硬件差異化,從而實(shí)現(xiàn)語言的跨平臺

4.1 重排序

  • 代碼重排序順序:源代碼->編譯器重排序->CPU層面重排序(指令級、內(nèi)存)->最終執(zhí)行的指令
  • 通過重排序可以提高代碼效率,但不是所用情況都會進(jìn)行重排序,是否重排序取決于【數(shù)據(jù)依賴規(guī)則】
/**
* 無數(shù)據(jù)依賴
* 1&2行代碼間無相互依賴
* 可進(jìn)行從排序
*/
int a = 1;
int b = 2;


/**
* 部分?jǐn)?shù)據(jù)依賴
*  1&2 行代碼間無數(shù)據(jù)依賴
*  1&3 行代碼間存在數(shù)據(jù)依賴
*  2&3 行代碼間存在數(shù)據(jù)依賴
* 1&2行可進(jìn)行重排序 1&3 2&3 行不可重排序
*/
int a=1;
int b = 2;
int c = a+b;
  • 數(shù)據(jù)依賴規(guī)則:as-if-serial
    無論代碼以何種方案進(jìn)行重排序,對于單個線程執(zhí)行代碼的結(jié)果不可變

  • Happens-Befor
    代碼A代碼的執(zhí)行結(jié)果對于B代碼必須是可見,就成為 A Happens-Befor B

  • 那些場景會觸發(fā)Happens-Before規(guī)則?
    ① 【程序的順序規(guī)則】

/**
* 單線程調(diào)用該方法時,A Happens-Befor B
**/
function X(){
 a =1;// A
 b =2;// A
}

② 【volatile規(guī)則】
volatile修飾的變量寫操作一定對讀操作可見,即 "寫" Happens-Befor "讀"
③【傳遞性規(guī)則】
如果 :A Happens-Befor B & B Happens-Befor C
那么: A Happens-Befor C
④ 【start規(guī)則】
主線程里的start()方法 Happens-Befo 該線程run方法內(nèi)任意代碼

/**
*  B Happens-Befor C (start規(guī)則)
*  A Happens-Befor B (順序規(guī)則)
*  A  Happens-Befor C  (傳遞性規(guī)則)
**/
public class A{
  static x=0;
  public static  void main(String []args){
    Thread t1=new Thread(()->{
      //C .....
    });
    x=10;//A
    t1.start();//B
  }
}

⑤【Join規(guī)則】
線程run方法內(nèi)代碼 Happens-Befor join()后的代碼

public class Demo {
    
    static  int a = 0;
    /**
     * A Happens-Befor B
    **/
    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(()->{
            a = 99;//A
        });
        t1.start();
        t1.join();
        System.out.println(a);//B
    }
}

⑥ 【synchronized監(jiān)視器鎖規(guī)則】
synchronized 的占用順序決定線程代碼順序

public class Demo {
    public  void  xx(){
        synchronized (this){
            //A...
        }
    }

    public static void main(String[] args) {
        
        /**
         *t1 線程t1 代碼A Happens-Befor 線程t2 代碼A
         *
         **/
        Demo demo = new Demo();
        Thread t1 = new Thread(()-> demo.xx());
        Thread t2 = new Thread(()-> demo.xx());
        t1.start();
        t1.join();//保證t1先于t2
        t2.start();
    }
}
  • 無數(shù)據(jù)依賴情況下禁止重排序
    當(dāng)代碼間不存在數(shù)據(jù)依賴,但在多線程調(diào)用的場景下可能會導(dǎo)致執(zhí)行結(jié)果錯誤,此時需要人工干預(yù)重排序---JMM內(nèi)存屏障
value =3;
void cup0{
  value = 10;// 通過storebuffer異步通知其他cpu緩存,將緩存value變?yōu)镮:失效狀態(tài)
  isFinish = true; //E 獨(dú)占狀態(tài)
}

void cup1{
    if(isFinish){//true 
        assert value == 10;//false
    }
}
  • JMM內(nèi)存屏障:編譯器級別內(nèi)存屏障、CPU級別內(nèi)存屏障

4.2 JMM解決有序性、可見性方案

  • volatile
    可解決可見性。通過內(nèi)存屏障實(shí)現(xiàn)
  • synchronized
    可解決可見性、有序性、原子性。通過對線程阻塞實(shí)現(xiàn)單線程調(diào)用來實(shí)現(xiàn)
  • final
    遍歷不可變,避免了可見性、原子性等問題
  • happens-before

5 線程的順序執(zhí)行

  • 使用join
    阻塞主線程,直到調(diào)用join()方法的線程執(zhí)行完畢;或者說調(diào)用join()線程的執(zhí)行結(jié)果對主線程可見,底層通過wait/notify實(shí)現(xiàn)
/**
* 只有添加join后線程才會123依次執(zhí)行
**/
Thread t1 = new Thread(()->{
    //doSomething1
});
Thread t2 = new Thread(()->{
    //doSomething2
});
Thread t3 = new Thread(()->{
    //doSomething3
});
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容