1. 程式人生 > >Java並發(一)-了解線程安全

Java並發(一)-了解線程安全

println 發現 java並發編程 發的 for 混合 private 和我 情況

線程不安全性

先來舉例說明線程不安全是什麽情況下發生的:例如一個變量可以被多個線程進行訪問,那麽在大量線程並發訪問這個變量的情況下,線程執行的順序會給最後的結果帶來不可預估的錯誤。
先定義一個單例類SimpleWorkingHardSingleton:

public class SimpleWorkingHardSingleton {
    private static SimpleWorkingHardSingleton simpleSingleton = new SimpleWorkingHardSingleton();
    
    // 數量
    private int count;
    
    private SimpleWorkingHardSingleton() {
        count = 0;
    }
    
    public static SimpleWorkingHardSingleton getInstance() {
        return simpleSingleton;
    }

    public int getCount() {
        return count;
    }
    
    public void addCount(int increment) {
        this.count += increment;
        System.out.println(this.count);
    }

}

可以看到下面這個單例若在多線程環境下運行,count是被多個線程同時操縱的變量,示例:

for (int i = 0; i < 5; i++) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            SimpleWorkingHardSingleton simpleSingleton = SimpleWorkingHardSingleton.getInstance();
            simpleSingleton.addCount(1);
        }
    }).start();
}

看輸出結果(你的執行結果可能和我的不同):

3
2
2
4
5

匪夷所思的結果,想不懂為什麽會有兩個2出現,1哪兒去了,為什麽輸出不是 1 2 3 4 5,下面就來解釋一下

  1. 為什麽沒有1?
    可能是a線程算出count=1,然後輸出count的時候,此時a線程掛起,b線程執行,b線程對count自增,此時a線程再輸出的時候,count已經發生了變化,這就導致了1沒有被輸出
  2. 為什麽兩個2?
    可能是a,b兩個線程都完成了count的計算,然後a線程輸出,輸出結束後立即被掛起,然後緊接著b線程立即也進行了輸出,那麽此時a b線程一定是輸出了相同的count,就導致了相同值的出現。
  3. 將循環次數加大到100或者200,就會發現最後輸出的count並不會到100或者200
    這是由於count++這個操作也不是原子的,也就是說count++並不是一次性完成,而是分為3步驟。第一步,獲取count;第二步,給count指向的值加1;第三步,講計算結果寫回count。因此如果三個步驟再混合上多線程執行順序的錯亂因素,就會導致不可預測的問題了。比如a線程獲取count為1,此時a線程立馬被掛起,b線程獲取count也為1,然後a,b線程各自去執行,最後寫回count都是2,這就導致了count被少加一次。

線程安全性

其實我們看到線程安全性的定義的關鍵點在於正確性,即在多線程的環境下,無論運行時候環境采用如何的調度方式,系統或者類或者方法總能表現出預期的相符的行為,那麽就是線程安全的。

總結

  1. 在多線程環境下,之所以會出現並發的線程安全性問題,是由於多個線程去操縱一個共享的變量或者一組變量,而且變量的操作過程不是原子的,那麽線程的執行順序就會幹擾到變量。
  2. 為了保證線程安全性,解決方法:
    • 多個線程訪問一個不可變量
    • 變量不可以被多線程共享
    • 線程做同步處理(原子性處理)

參考內容

  1. 書籍《Java並發編程實戰》

Java並發(一)-了解線程安全