1. 程式人生 > >java併發之如何解決執行緒安全問題

java併發之如何解決執行緒安全問題

併發(concurrency)一個並不陌生的詞,簡單來說,就是cpu在同一時刻執行多個任務。

而Java併發則由多執行緒實現的。

在jvm的世界裡,執行緒就像不相干的平行空間,序列在虛擬機器中。(當然這是比較籠統的說法,執行緒之間是可以互動的,他們也不一定是序列。)

多執行緒的存在就是壓榨cpu,提高程式效能,還能減少一定的設計複雜度(用現實的時間思維設計程式)。

這麼說來似乎執行緒就是傳說中的銀彈了,可事實告訴我們真正的銀彈並不存在。

多執行緒會引出很多難以避免的問題, 如死鎖,髒資料,執行緒管理的額外開銷,等等。更大大增加了程式設計的複雜度。

但他的優點依舊不可替代。

 

死鎖髒資料就是典型的執行緒安全問題。

簡單來說,執行緒安全就是: 在多執行緒環境中,能永遠保證程式的正確性。

只有存在共享資料時才需要考慮執行緒安全問題。 

java記憶體區域:

其中, 方法區就是主要的執行緒共享區域。那麼就是說共享物件只可能是類的屬性域或靜態域。

 

瞭解了執行緒安全問題的一些基本概念後, 我們就來說說如何解決執行緒安全問題。我們來從一個簡單的servlet示例來分析:

 

複製程式碼
public class ReqCounterServlet extends HttpServlet{
    private int count = 0;
    
    public void doGet(HttpServletRequest request, 
        HttpServletResponse response) throws IOException, ServletException {
        count++;
        System.out.print("當前已達到的請求數為" + count);
    }
    
    public void doPost(HttpServletRequest request, 
        HttpServletResponse response) throws IOException, ServletException {
        // ignore
    }
}
複製程式碼

1. 瞭解業務場景的執行緒模型

這裡的執行緒模型指的是: 在該業務場景下, 可能出現的執行緒呼叫實況。

眾所周知,Servlet是被設計為單例項,在請求進入tomcat後,由Connector建立連線,再講請求分發給內部執行緒池中的Processor,

此時Servlet就處於一個多執行緒環境。即如果存在幾個請求同時訪問某個servlet,就可能會有幾個執行緒同時訪問該servlet物件。如圖:

執行緒模型,如果簡單的話,就在腦海模擬一下就好了,複雜的話就可以用紙筆或其他工具畫出來。

 

2. 找出共享物件

這裡的共享物件就很明顯就是ReqCounterServlet。

 

3. 分析共享物件的不變性條件

不變性條件,這個名詞是在契約式程式設計的概念中的。不變性條件保證類的狀態在任何功能被執行後都保持在一個可接受的狀態。

這裡可以引申出, 不可變物件是執行緒安全的。(因為不可變物件就沒有不變性條件)

不變性條件則主要由對可變狀態的修改與訪問構成。

這裡的servlet很簡單, 不變性條件大致可以歸納為: 每次請求進入時count計數必須加一,且計數必須正確。

在複雜的業務中, 類的不變性條件往往很難考慮周全。設計的世界是險惡的,只能小心謹慎,用測量去證明,最大程度地減少錯誤出現的機率。

 

4. 用特定的策略解決執行緒安全問題。 

如何解決的確是該流程的重點。目前分三種方式解決:

第一種,修改執行緒模型。即不線上程之間共享該狀態變數。一般這個改動比較大,需要量力而行。

第二種,將物件變為不可變物件。有時候實現不了。

第三種,就比較通用了,在訪問狀態變數時使用同步。 synchronized和Lock都可以實現同步。簡單點說,就是在你修改或訪問可變狀態時加鎖,獨佔物件,讓其他執行緒進不來。

這也算是一種執行緒隔離的辦法。(這種方式也有不少缺點,比如說死鎖,效能問題等等)

 

 

 

 

其實有一種更好的辦法,就是設計執行緒安全類。《程式碼大全》就有提過,問題解決得越早,花費的代價就越小。

是的,在設計時,就考慮執行緒安全問題會容易的多。

首先考慮該類是否會存在於多執行緒環境。如果不是,則不考慮執行緒安全。

然後考慮該類是否能設計為不可變物件,或者事實不可變物件。如果是,則不考慮執行緒安全

最後,根據流程來設計執行緒安全類。

設計執行緒安全類流程:

1、找出構成物件狀態的所有變數。

2、找出約束狀態變數的不變性條件。

3、建立物件狀態的併發訪問管理策略。

 

有兩種常用的併發訪問管理策略:

1、java監視器模式。  一直使用某一物件的鎖來保護某狀態。

2、執行緒安全委託。  將類的執行緒安全性委託給某個或多個執行緒安全的狀態變數。(注意多個時,這些變數必須是彼此獨立,且不存在相關聯的不變性條件。) 

 

同步策略(文件化很重要):

  定義瞭如何在不違背物件不變條件或後驗條件的情況下對其狀態的訪問操作進行協同。同步策略規定了如何將不可變性,執行緒封閉,與加鎖機制等結合起來以維護執行緒的安全性,並且還規定了哪些變數由哪些鎖保護。

 

from: https://www.cnblogs.com/w2154/p/6637717.html