線程安全ThreadLocal和synchronized

線程安全問題的由來

在傳統的Web開發中,我們處理Http請求最常用的方式是通過實現Servlet對象來進行Http請求的響應。Servlet是J2EE的重要標準之一,規定了Java如何響應Http請求的規範。通過HttpServletRequest和HttpServletResponse對象,我們能夠輕鬆地與Web容器交互。

當Web容器收到一個Http請求時,Web容器中的一個主調度線程會從事先定義好的線程中分配一個當前工作線程,將請求分配給當前的工作線程,由該線程來執行對應的Servlet對象中的service方法。當這個工作線程正在執行的時候,Web容器收到另外一個請求,主調度線程會同樣從線程池中選擇另外一個工作線程來服務新的請求。Web容器本身並不關心這個新的請求是否訪問的是同一個Servlet實例。因此,我們可以得出一個結論:對於同一個Servlet對象的多個請求,Servlet的service方法將在一個多線程的環境中併發執行。所以,Web容器默認採用單實例(單Servlet實例)多線程的方式來處理Http請求。這種處理方式能夠減少新建Servlet實例的開銷,從而縮短了對Http請求的響應時間。但是,這樣的處理方式會導致變量訪問的線程安全問題。也就是說,Servlet對象並不是一個線程安全的對象。


(題外話)此時還想到了面試經常談起的HashMap和HashTable線程安全問題,在此也累贅的說上幾句

HashMap是基於HashTable的一種Map實現,允許null values和null key(它跟HashTable的區別在於它是非同步的,而且允許null key和null values),HashMap不保證Map的順序,甚至不能保證Map的順序在運行過程中保持不變。


HashMap不是線程安全的

hastmap是一個接口是map接口的子接口,是將鍵映射到值的對象,其中鍵和值都是對象,並且不能包含重複鍵,但可以包含重複值。HashMap允許null key和null value,而hashtable不允許。


HashTable是線程安全的一個Collection。

HashMap是Hashtable的輕量級實現(非線程安全的實現),他們都完成了Map接口,主要區別在於HashMap允許空(null)鍵值(key),由於非線程安全,效率上可能高於Hashtable。

HashMap允許將null作爲一個entry的key或者value,而Hashtable不允許。

HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因爲contains方法容易讓人引起誤解。

Hashtable繼承自Dictionary類,而HashMap是Java1.2引進的Map interface的一個實現。

最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多個線程訪問Hashtable時,不需要自己爲它的方法實現同步,而HashMap 就必須爲之提供外同步。

Hashtable和HashMap採用的hash/rehash算法都大概一樣,所以性能不會有很大的差異。

------------------------------------------------------繼續上面的話題--------------------------------------------------

有關線程安全的概念範疇

概念:

如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。

線程安全問題都是由全局變量及靜態變量引起的。若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。


線程安全,指的是在多線程環境下,一個類在執行某個方法時,對類的內部實例變量的訪問是安全的。因此,對於下面列出來的2類變量,不存在任何線程安全的說法:

1)方法簽名中的任何參數變量。

2)處於方法內部的局部變量。

  任何針對上述形式的變量的訪問都是線程安全的,因爲他們都是處於方法的內部,由當前的執行線程獨自管理。

這就是線程安全問題的由來:在傳統的基於Servlet的開發模式中,Servlet對象內部的實例變量不是線程安全。在多線程環境中,這些變量的訪問需要通過特殊的手段進行訪問控制。

  解決線程安全訪問的方法很多,比較容易想到的一種方案是使用同步機制(Synchronized),但是出於對Web應用效率的考慮,這種機制在Web開發中的可行性很低,也違背了Servlet的設計初衷。因此,我們需要另闢蹊徑來解決這一困擾我們的問題。

ThreadLocal

在JDK的早期版本中,提供了一種解決多線程併發問題的方案:java.lang.ThreadLocal類。ThreadLocal類在維護變量時,實際使用了當前線程(Thread)中的一個叫做ThreadLocalMap的獨立副本,每個線程可以獨立修改屬於自己的副本而不會互相影響,從而隔離了線程和線程,避免了線程訪問實例變量發生衝突的問題。

ThreadLocal本身並不是一個線程,而是通過操作當前線程中的一個內部變量來達到與其他線程隔離的目的。之所以取名爲ThreadLocal,所期望表達的含義是其操作的對象是線程的一個本地變量。

ThradLocal模式至少從兩個方面完成了數據訪問隔離,即橫向隔離和縱向隔離。有了橫向和縱向兩種不同的隔離方式,ThradLocal模式就能真正地做到線程安全。

縱向隔離----線程與線程之間的數據訪問隔離。這一點由線程的數據結構保證。因爲每個線程在進行對象的訪問時,訪問的都是各個線程自己的ThradLocalMap。

橫向隔離----同一個線程中,不同的ThradLocal實例操作的對象之間相互隔離。這一點由ThradLocalMap在存儲時採用當前ThradLocal的實例作爲key來保證。

深入比較TheadLocal模式與synchronized關鍵字

ThreadLocal模式synchronized關鍵字都用於處理多線程併發訪問變量的問題,只是二者處理問題的角度和思路不同。

1)ThreadLocal是一個java類,通過對當前線程中的局部變量的操作來解決不同線程的變量訪問的衝突問題。所以,ThreadLocal提供了線程安全的共享對象機制,每個線程都擁有其副本。

2)Java中的synchronized是一個保留字,它依靠JVM的鎖機制來實現臨界區的函數或者變量的訪問中的原子性。在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。此時,被用作“鎖機制”的變量時多個線程共享的。

同步機制(synchronized關鍵字)採用了“以時間換空間”的方式,提供一份變量,讓不同的線程排隊訪問。而ThreadLocal採用了“以空間換時間”的方式,爲每一個線程都提供一份變量的副本,從而實現同時訪問而互不影響。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章