java併發之如何解決線程安全問題

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

而Java併發則由多線程實現的。編程

在jvm的世界裏,線程就像不相干的平行空間,串行在虛擬機中。(固然這是比較籠統的說法,線程之間是能夠交互的,他們也不必定是串行。)tomcat

多線程的存在就是壓榨cpu,提升程序性能,還能減小必定的設計複雜度(用現實的時間思惟設計程序)。安全

這麼說來彷佛線程就是傳說中的銀彈了,可事實告訴咱們真正的銀彈並不存在。多線程

多線程會引出不少難以免的問題, 如死鎖,髒數據,線程管理的額外開銷,等等。更大大增長了程序設計的複雜度。併發

但他的優勢依舊不可替代。jvm

 

死鎖髒數據就是典型的線程安全問題。工具

簡單來講,線程安全就是: 在多線程環境中,能永遠保證程序的正確性。性能

只有存在共享數據時才須要考慮線程安全問題。 spa

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均可以實現同步。簡單點說,就是在你修改或訪問可變狀態時加鎖,獨佔對象,讓其餘線程進不來。

這也算是一種線程隔離的辦法。(這種方式也有很多缺點,好比說死鎖,性能問題等等)

 

 

 

 

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

是的,在設計時,就考慮線程安全問題會容易的多。

首先考慮該類是否會存在於多線程環境。若是不是,則不考慮線程安全。

而後考慮該類是否能設計爲不可變對象,或者事實不可變對象。若是是,則不考慮線程安全

最後,根據流程來設計線程安全類。

設計線程安全類流程:

一、找出構成對象狀態的全部變量。

二、找出約束狀態變量的不變性條件。

三、創建對象狀態的併發訪問管理策略。

 

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

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

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

 

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

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

相關文章
相關標籤/搜索