首頁 Java java教程 Spring下單例模式與線程安全之間的矛盾解決

Spring下單例模式與線程安全之間的矛盾解決

Oct 22, 2018 pm 05:30 PM
java spring 多執行緒 安全

這篇文章帶給大家的內容是關於Spring下單例模式與線程安全之間的矛盾解決,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

有多少人在使用Spring框架時,很多時候不知道或忽略了多執行緒的問題?

因為寫程式時,或做單元測試時,很難有機會碰到多執行緒的問題,因為沒有那麼容易模擬多執行緒測試的環境。那麼當多個執行緒呼叫同一個bean的時候就會存在線程安全問題。如果是Spring中bean的創建模式為非單例的,也就不存在這樣的問題了。

但如果不去考慮潛在的漏洞,它就會變成程式的隱形殺手,在你不知道的時候爆發。而且,通常是程式交付使用時,在生產環境下觸發,會是很麻煩的事。

Spring使用ThreadLocal解決線程安全問題

我們知道在一般情況下,只有無狀態的Bean可以在多執行緒環境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因為Spring對某些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態採用ThreadLocal進行處理,讓它們也成為線程安全的狀態,因為有狀態的Bean就可以在多線程中共享了。

一般的Web應用劃分為展現層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層透過介面向上層開放功能呼叫。在一般情況下,從接收請求到回傳回應所經過的所有程式呼叫都同屬於一個執行緒。

ThreadLocal是解決執行緒安全性問題一個很好的思路,它透過為每個執行緒提供一個獨立的變數副本解決了變數並發存取的衝突問題。在許多情況下,ThreadLocal比直接使用synchronized同步機制解決執行緒安全性問題更簡單,更方便,且結果程式擁有更高的並發性。

如果你的程式碼所在的進程中有多個執行緒在同時執行,而這些執行緒可能會同時運行這段程式碼。如果每次運行結果和單執行緒運行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。或者說:一個類別或程式所提供的介面對於執行緒來說是原子操作或是多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。 線程安全問題都是由全域變數及靜態變數引起的。

若每個執行緒中對全域變數、靜態變數只有讀取操作,而無寫操作,一般來說,這個全域變數是執行緒安全的;若有多個執行緒同時執行寫入操作,一般都需要考慮線程同步,否則就可能影響線程安全。
1) 常數總是執行緒安全的,因為只存在讀取操作。
2)每次呼叫方法前都會新建一個實例是執行緒安全的,因為不會存取共享的資源。
3)局部變數是線程安全的。因為每執行一個方法,都會在獨立的空間創建局部變量,它不是共享的資源。局部變數包括方法的參數變數和方法內變數。

有狀態就是有資料儲存功能。有狀態物件(Stateful Bean),就是有實例變數的物件  ,可以保存數據,是非線程安全的。在不同方法呼叫間不保留任何狀態。

無狀態就是一次操作,不能儲存資料。無狀態物件(Stateless Bean),就是沒有實例變數的物件  .不能保存數據,是不變類,是執行緒安全的。

有狀態物件:

無狀態的Bean適合用不變模式,技術就是單例模式,這樣可以共享實例,提高效能。有狀態的Bean,多執行緒環境下不安全,那麼適合用Prototype原型模式。 Prototype: 每次對bean的請求都會建立一個新的bean實例。

Struts2預設的實作是Prototype模式。也就是每個請求都新產生一個Action實例,所以不存在線程安全問題。要注意的是,如果Spring管理action的生命週期, scope要配成prototype作用域

執行緒安全案例

##SimpleDateFormat( 下面簡稱 sdf) 類別內部有一個 Calendar 對象引用 , 它用來儲存和這個 sdf 相關的日期資訊 , 例如 sdf.parse(dateStr), sdf.format(date)  Calendar 引用來儲存的 . 這樣就會導致一個問題 , 如果你的 sdf 是個 static 的 ,  那麼多 thread  與你分享這個 sdf, 你會發現有以下的呼叫 :

 Date parse() {
   calendar.clear(); // 清理calendar
   ... // 执行一些操作, 设置 calendar 的日期什么的
   calendar.getTime(); // 获取calendar的时间
 }
登入後複製

這裡會導致的問題就是 ,  如果 線程 A  調用了  sdf.parse(),  並且進行了 calendar.clear() 後未執行 calendar.getTime()  ). (), 這時候線程 B 也執行了 sdf.clear() 方法 ,  這樣就導致線程 A 的的 calendar 資料被清空了 ( 實際上 A,B clear()  後被掛起 ,  這時候 B  開始調用 sdf.parse() 並順利 i 結束 ,  這樣  A  的設定對的問題 背後隱藏著一個更重要的問題 -- 無狀態:無狀態方法的好處之一,就是它在各種環境下,都可以安全的呼叫。衡量一個方法是否是有狀態的,就看它是否改動了其它的東西,例如全域變量,例如實例的欄位。 format 方法在運作過程中改變了SimpleDateFormat 的 calendar 字段,所以,它是有狀態的。

這也同時提醒我們在開發和設計系統的時候注意下以下三點 :

#自己寫公用類別的時候,要對多執行緒呼叫情況下的後果在註解裡進行明確說明

對線程環境下,對每一個共享的可變變數都要注意其線程安全性

我們的類別和方法在做設計的時候,要盡量設計成無狀態的

解決方案

1. 需要的時候建立新實例:

說明:在需要用到 SimpleDateFormat的地方新建一個實例,不管什麼時候,將有線程安全問題的物件由共享變為局部私有都能避免多線程問題,不過也加重了創建物件的負擔。在一般情況下,這樣其實對效能影響比不是很明顯的。

2. 使用同步:同步 SimpleDateFormat 物件

public class DateSyncUtil {
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      
    public static String formatDate(Date date)throws ParseException{
        synchronized(sdf){
            return sdf.format(date);
        }  
    }
    
    public static Date parse(String strDate) throws ParseException{
        synchronized(sdf){
            return sdf.parse(strDate);
        }
    } 
}
登入後複製
說明:當執行緒較多時,當一個執行緒呼叫該方法時,其他想要呼叫此方法的執行緒就要block ,多執行緒並發量大的時候會對效能有一定的影響。

3. 使用 ThreadLocal :

public class ConcurrentDateUtil {
    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    public static Date parse(String dateStr) throws ParseException {
        return threadLocal.get().parse(dateStr);
    }
    public static String format(Date date) {
        return threadLocal.get().format(date);
    }
}
登入後複製

ThreadLocal<DateFormat>(); 
 
    public static DateFormat getDateFormat()   
    {  
        DateFormat df = threadLocal.get();  
        if(df==null){  
            df = new SimpleDateFormat(date_format);  
            threadLocal.set(df);  
        }  
        return df;  
    }  
    public static String formatDate(Date date) throws ParseException {
        return getDateFormat().format(date);
    }
    public static Date parse(String strDate) throws ParseException {
        return getDateFormat().parse(strDate);
    }   
}
登入後複製

說明:使用 ThreadLocal,  也是將共享變數變成獨享,執行緒獨享肯定能比方法獨享在並發環境中能減少不少創建物件的開銷。如果在效能要求比較高的情況下,一般建議使用此方法。

4. 拋棄 JDK ,使用其他類別庫中的時間格式化類別:

使用 Apache commons  裡的 FastDateFormat ,並宣稱有快速且執行緒的SimpleDateFormatat ,  可惜它只能對日期進行 format,  不能對日期串進行解析。

使用 Joda-Time 類別函式庫來處理時間相關問題

做一個簡單的壓力測試,方法一最慢,方法三最快,但是就算是最慢的方法一效能也不差,一般系統方法一和方法二就可以滿足,所以說這個點很難成為你係統的瓶頸所在。從簡單的角度來說,建議使用方法一或方法二,如果在必要的時候,追求那麼一點性能提升的話,可以考慮用方法三,用 ThreadLocal 做緩存。

Joda-Time 類別庫對時間處理方式比較完美,建議使用。

總結

回到文章開頭的問題:《有多少人在使用Spring框架時,很多時候不知道或忽略了多執行緒的問題? 》

其實程式碼誰都會寫,為什麼架構師寫的程式碼效果和你的天差地別呢?應該就是這類你沒考慮到的小問題而架構師都考慮到了。

架構師知識面更廣,見識到的具體情況更多,解決各類問題的經驗更豐富。只要養成架構師的思維和習慣,那你離架構師還會遠嗎?

以上是Spring下單例模式與線程安全之間的矛盾解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP與Python:了解差異 PHP與Python:了解差異 Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

Java程序查找膠囊的體積 Java程序查找膠囊的體積 Feb 07, 2025 am 11:37 AM

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHP與其他語言:比較 PHP與其他語言:比較 Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP與Python:核心功能 PHP與Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

創造未來:零基礎的 Java 編程 創造未來:零基礎的 Java 編程 Oct 13, 2024 pm 01:32 PM

Java是熱門程式語言,適合初學者和經驗豐富的開發者學習。本教學從基礎概念出發,逐步深入解說進階主題。安裝Java開發工具包後,可透過建立簡單的「Hello,World!」程式來實踐程式設計。理解程式碼後,使用命令提示字元編譯並執行程序,控制台上將輸出「Hello,World!」。學習Java開啟了程式設計之旅,隨著掌握程度加深,可創建更複雜的應用程式。

如何在Spring Tool Suite中運行第一個春季啟動應用程序? 如何在Spring Tool Suite中運行第一個春季啟動應用程序? Feb 07, 2025 pm 12:11 PM

Spring Boot簡化了可靠,可擴展和生產就緒的Java應用的創建,從而徹底改變了Java開發。 它的“慣例慣例”方法(春季生態系統固有的慣例),最小化手動設置

See all articles