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

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

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

這篇文章帶給大家的內容是關於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);
    }
}</dateformat></dateformat>

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);
    }   
}</dateformat>

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

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

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

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

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

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

總結

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

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

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

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

陳述
本文轉載於:segmentfault思否。如有侵權,請聯絡admin@php.cn刪除
說明JVM如何充當Java代碼和基礎操作系統之間的中介。說明JVM如何充當Java代碼和基礎操作系統之間的中介。Apr 29, 2025 am 12:23 AM

JVM的工作原理是將Java代碼轉換為機器碼並管理資源。 1)類加載:加載.class文件到內存。 2)運行時數據區:管理內存區域。 3)執行引擎:解釋或編譯執行字節碼。 4)本地方法接口:通過JNI與操作系統交互。

解釋Java虛擬機(JVM)在Java平台獨立性中的作用。解釋Java虛擬機(JVM)在Java平台獨立性中的作用。Apr 29, 2025 am 12:21 AM

JVM使Java實現跨平台運行。 1)JVM加載、驗證和執行字節碼。 2)JVM的工作包括類加載、字節碼驗證、解釋執行和內存管理。 3)JVM支持高級功能如動態類加載和反射。

您將採取哪些步驟來確保Java應用程序在不同的操作系統上正確運行?您將採取哪些步驟來確保Java應用程序在不同的操作系統上正確運行?Apr 29, 2025 am 12:11 AM

Java應用可通過以下步驟在不同操作系統上運行:1)使用File或Paths類處理文件路徑;2)通過System.getenv()設置和獲取環境變量;3)利用Maven或Gradle管理依賴並測試。 Java的跨平台能力依賴於JVM的抽象層,但仍需手動處理某些操作系統特定的功能。

Java是否需要特定於平台的配置或調整區域?Java是否需要特定於平台的配置或調整區域?Apr 29, 2025 am 12:11 AM

Java在不同平台上需要進行特定配置和調優。 1)調整JVM參數,如-Xms和-Xmx設置堆大小。 2)選擇合適的垃圾回收策略,如ParallelGC或G1GC。 3)配置Native庫以適應不同平台,這些措施能讓Java應用在各種環境中發揮最佳性能。

哪些工具或庫可以幫助您解決Java開發中特定於平台的挑戰?哪些工具或庫可以幫助您解決Java開發中特定於平台的挑戰?Apr 29, 2025 am 12:01 AM

Osgi,Apachecommonslang,JNA和JvMoptionsareeForhandlingForhandlingPlatform-specificchallengesinjava.1)osgimanagesdeppedendendencenciesandisolatescomponents.2)apachecommonslangprovidesitorityfunctions.3)

JVM如何在不同平台上管理垃圾收集?JVM如何在不同平台上管理垃圾收集?Apr 28, 2025 am 12:23 AM

JVMmanagesgarbagecollectionacrossplatformseffectivelybyusingagenerationalapproachandadaptingtoOSandhardwaredifferences.ItemploysvariouscollectorslikeSerial,Parallel,CMS,andG1,eachsuitedfordifferentscenarios.Performancecanbetunedwithflagslike-XX:NewRa

為什麼Java代碼可以在不同的操作系統上運行,而無需修改?為什麼Java代碼可以在不同的操作系統上運行,而無需修改?Apr 28, 2025 am 12:14 AM

Java代碼可以在不同操作系統上無需修改即可運行,這是因為Java的“一次編寫,到處運行”哲學,由Java虛擬機(JVM)實現。 JVM作為編譯後的Java字節碼與操作系統之間的中介,將字節碼翻譯成特定機器指令,確保程序在任何安裝了JVM的平台上都能獨立運行。

描述編譯和執行Java程序的過程,突出平台獨立性。描述編譯和執行Java程序的過程,突出平台獨立性。Apr 28, 2025 am 12:08 AM

Java程序的編譯和執行通過字節碼和JVM實現平台獨立性。 1)編寫Java源碼並編譯成字節碼。 2)使用JVM在任何平台上執行字節碼,確保代碼的跨平台運行。

See all articles

熱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

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

熱工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具