一)併發編程的優勢
提高了計算機設備的資源利用率
程序的基本調度單位是線程,在多核處理時代,併發編程大大提高了cpu的使用率
計算多任務比單任務串行程序更加便利
對於一些窗口類程序跟web操控頁面,併發編程能可以把操作任務放到後臺的獨立多個線程裡,這種異步操作處理,大大提高了用戶界面的響應速度
建模更加簡單
在串行變成的年代完成一個複雜任務的情況下,複雜任務分解的幾個小任務必須是串行執行,對一類事情進行程序建模是比較困難的。在併發情況下,一個複雜任務分解成的每個小任務可以單獨一個線程進行執行,這樣針對某類業務的建模會更見簡單
併發變成的風險
併發編程是把雙刃劍,處理不得當就會遇到以下問題
1.安全問題
線程安全問題是現在java開發人員經常會遇到的問題,尤其是互聯網企業裡!
例如如下:一個自增的程序在多線程情況下就會出現計數不准問題
public class Sequence{
private int value; /**返回下一個序列**/
public int getNext(){
return value++;
}
}
上面程序是因為在不同線程之間進行交替執行情況下很有可能出現結果與預期的不一樣
2.性能問題
並不是多線程就一定能提高程序性能。線程總會帶來某種程度上的運行時開銷
二)線程安全性
所謂線程安全性的程序主要就是要對訪問狀態進行控制管理,尤其是對共享與可變狀態的訪問
共享:意味著多個線程可以同時訪問
可變:意味著變量的值在其生命週期內發生變化
所以所謂的線程安全的概念定義:就是在多個線程訪問同一個類或者可變變量,進行寫入操作的時候,通過同步機制來協同管理對類與變量的訪問,能達到多線程訪問情況下,某個類或者變量始終能輸出正確的結果,那麼這個程序就是所謂的線程安全的
線程安全的手段
不在線程間存在共享變量
把共享變量變成final不可修改類型
在訪問共享變量情況下,使用同步
前兩種手段無需贅述,本博客一系列文章都會圍繞第三種手段進行講解!
一些概念
原子性:
所謂的原子性就是一個操作不能進行再分解。
我們舉例說明:還是上個描述了一個返回自增序列的程序:
public class Sequence{
private int value;
/**返回下一個序列**/
public int getNext(){
return value++;
}
}
getNext方法裡針對value++操作其實可以分解成三步(獲取-修改-寫入)
讀取value的值
value進行+1操作計算結果
把結果寫入value
所以這個value++就不是具有原子性
竟態條件
在多個線程交替執行的時候就會發生竟態條件,還是上面的例子進行說明,value++被分解的三步,其實每一步都依賴於上一步的結果來進行下一步的操作,但是在多線程操作性,1 2 3執行時序會錯亂,這就會導致結果會出現不正確的結果,類似這種的情況就被稱為竟態條件
典型的竟態條件就是“先檢查後執行”操作,通過一個可能錯誤的結果來決定下一步的動作。
我們日常編碼過程中最經常發生竟態條件的就是那種“懶加載”(延遲加載)的代碼程序
/**
懶加載程序示例
**/
public class LazyInit{
private LazyInit instance=null;
public static getInstance(){
if(instance==null){
instance=new LazyInit();
}
return instance;
}
}
例子中假如線程a 獲取instance停在了 8行,正要執行第9行的時候,交出了執行權,線程b進來一路執行,也判斷instance為null,new了一個實例並且進行了返回。然後線程a獲得執行權,也new了一個實例要進行了返回。兩個線程獲取的實例不是同一個對象。這顯然也是錯誤的。
複合操作與加鎖機制
之前例子的value++要想獲得正確的結果,必須通過同步手段把實際上的三步變成原子一起執行。把這類多步驟變成原子性的操作成為複合操作,而同步手段稱作加鎖機制
java提供了內置的鎖機制來支持原子性:同步代碼塊(synchroinzed)jdk1.5 Concurrent包提供了Lock也可以來支持原子性操作