Java多線程併發學習(一)線程安全性

線程安全性

github學習筆記傳送門

  • df: 當多個線程訪問某個類時,不管運行時環境採用何種調度方式或者這些進程將如何交替執行,並且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行爲,那麼稱這個類是線程安全的

    • 原子性:提供了互斥訪問,同一時刻只能有一個線程來對他進行操作
    • 可見性:一個線程對主內存的修改可以及時的被線程觀察到
    • 有序性:一個線程觀察其他線程中的指令執行順序,由於指令重排序的存在,該觀察結果一般雜亂無序
    1. 原子性 -Atomic包

      • AtomicXXX: CAS,Unsafe.compareAndSwapInt
         // Java底層包、非Java實現
         public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
      
         // 拿當前對象的值和底層的值進行對比,如果相等才進行操作,如果不同則一直執行,直到相同
         public final int getAndAddInt(Object var1, long var2, int var4) {
           int var5;
                do {
                    var5 = this.getIntVolatile(var1, var2);
                } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        
                return var5;
            }
      
      
      • AtomicLong,LongAdder
        • LongAdder: AtomicLong的實現原理是:利用底層操作系統的CAS來保證原子性,在一個死循環內不斷執行CAS操作,直到操作成功。不過,CAS操作的一個問題是在併發量比較大的時候,可能很多次的執行CAS操作都不成功,這樣性能就受到較大影響。
          在ConcurrentHashMap中,對Map分割成多個segment,這樣多個Segment的操作就可以並行執行,從而可以提高性能。在JDK8中,LongAdder與ConcurrentHashMap類似,將內部操作數據value分離成一個Cell數組,每個線程訪問時,通過Hash等算法映射到其中一個Cell上。
          計算最終的數據結果,則是各個Cell數組的累計求和。
        • 缺點:在統計時如果有併發可能會導致統計的數據有所誤差
      • AtomicReference,AtomicReferenceFieldUpdater
        • AtomicExample5
      • AtomicStampReference: CAS的ABA問題
        • ABA問題:在進行CAS操作時,線程將A改爲B又改回A,每次修改時改變版本號
    2. 原子性-synchronized

      • 修飾代碼塊:大括號括起來的代碼,作用於調用的對象
      • 修飾方法:整個方法,作用於調用的對象
      • 修飾靜態方法:整個靜態方法,作用於所有對象
      • 修飾類:括號括起來的部分,作用於整個對象
      • 與lock的對比
        • synchronized:不可中斷鎖,適合競爭不激烈,可讀性好
        • Lock:可中斷鎖,多樣化同步,競爭激烈時能維持常態
        • Atomic:競爭激烈時能維持常態,比Lock性能要好;但只能同步一個值
    3. 可見性

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

      • 線程交叉執行
        • 重排序結合線程交叉執行
        • 共享變量更新後的值沒有在工作內存與主存間及時更新
        1. JVM關於synchronized的兩條規定
        2. 線程解鎖前,必須把共享變量的最新值刷新到主內存
        3. 線程加鎖時,將清空工作內存中共享變量的值,從而使用共享變量時需要從主內存中重新讀取最新的值(加鎖和解鎖是同一把鎖)
      1. 可見性 -volatile

        通過加入內存屏障和禁止重排序優化來實現

        1. 對volatile變量寫操作時,會在寫操作後加入一條store屏障指令,將本地內存中的共享變量值刷新到主內存
        2. 對volatile變量讀操作時,會在讀操作前加入一條load屏障指令,從主內存中讀取共享變量
        3. volatile使用 – 作爲標記量使用
        volatile boolean inited = false;
        
        // 線程1
        context = loadContext();
        inited = true;
        
        // 線程2
        while(!inited){
         sleep();
        }
        doSomethingWithConfig(context);
        
        
    4. 有序性

      Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序的過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性

      1. happens-before原則:

        程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生於書寫在後面的操作

      2. 鎖定規則:一個unLock操作先行發生與後邊對同一個鎖的Lock操作

      3. volatile變量規則:對一個變量的寫操作先行發生於後邊對這個變量的讀操作

      4. 傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C

      5. 線程啓動規則:Thread對象的start()方法先行發生於此線程的每一個動作

      6. 線程中斷原則:對線程的interrupt()方法的調用先行發生於被中斷線程的代碼檢測到的中斷事件的發生

      7. 線程終結原則:線程中所有的操作都先行發生與線程的終止檢測,可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行

      8. 對象終結規則:一個對象的初始化完成先行發生於他的finalize()方法的開始

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