什么是Java的線程安全問題?
線程安全就是多線程訪問時,采用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀/寫完,其他線程才可使用。不會出現數據不一致或者數據污染。
線程不安全就是不提供數據訪問保護,有可能出現多個線程先后更改數據造成所得到的數據是臟數據。
那么,理解下上面這段話,再拋出新的問題:
- 什么是臟數據?
- 什么情況會觸發線程安全問題?為什么靜態變量和線程安全結合緊密?
- 如何解決線程安全問題?
- 如何預防線程安全問題?
什么是臟數據?
先百度:臟數據;
簡單的說:
通俗的講,當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。因為這個數據是還沒有提交的數據,那么另外一個事務讀到的這個數據是臟數據,依據臟數據所做的操作可能是不正確的。
直觀的感覺有點像沖突。
什么情況會觸發線程安全問題?為什么靜態變量和線程安全結合緊密?
去網上找答案和分析,很多情況會看到這么一句話:
靜態變量類型設置的不合理會造成線程不安全。
黑人問號。。。
我們來看下造成線程不安全的幾個要素:
- 多線程應用
- 訪問同一塊/個數據;
多線程應用:一般來說基本上都是了;
訪問同一塊數據:這篇文章寫的很好,轉來分享:在多線程中使用靜態方法是否有線程安全問題
總而言之就是這樣子的:——》調用靜態方法——》調用靜態變量——》線程不安全
所以,直接引起線程不安全的是不安全的靜態變量,前面並不重要;
如何解決線程安全問題?
對症下葯:
- 不安全的變量——》安全的變量;
- 靜態——》動態(每次使用時生成)
后一個方法可以說是設計或者業務上的問題了,需要注意的是第一個,也就是哪些是線程安全,哪些線程不安全,還經常被用作靜態變量。
這里舉兩個碰到的例子:
- SimpleDateFormat,不安全;
- StringBuilder,不安全,StringBuffer,安全;
另外,也可以對大量代碼進行同步操作,但不是很推薦:
1、同步方法
給多線程訪問的成員方法加上synchronized修飾符
public void synchronized doWork(){ // TODO }
使用synchronized修飾的方法,就叫做同步方法,保證線程執行該方法的時候,其他線程只能在方法外等着。
2、同步代碼塊
synchronized(同步鎖對象) { // 需要同步操作的代碼 }
實際上,對象的同步鎖只是一個概念,可以想象為在對象上標記了一個鎖,誰拿到鎖,誰就可以進入代碼塊,其他線程只能在代碼塊外面等着,而且注意,在任何時候,Java虛擬機最多允許一個線程擁有該同步鎖。
Java程序運行可以使用任何對象作為同步監聽對象,但是一般的,我們把當前並發訪問的共同資源作為同步監聽對象。
實際上,同步方法和同步代碼塊差不了多少,在本質上是一樣的,兩者都用了一個關鍵字synchronized,synchronized保證了多線程並發訪問時的同步操作,避免線程的安全性問題,但是有一個弊端,就是使用synchronized的方法/代碼塊的性能比不用要低一些,因此如果要用synchronized,建議盡量減小synchronized的作用域。
如何預防線程安全問題?
其實這里想說的是是否需要在開發時對線程安全問題重點考慮。
為什么這么說呢?
比如StringBuilder和StringBuffer,在相應的API文檔中有這樣的描述:
將StringBuilder 的實例用於多個線程是不安全的。如果需要這樣的同步,則建議使用StringBuffer。”,提到StringBuffer時,說到“StringBuffer是線程安全的可變字符序列,一個類似於String的字符串緩沖區,雖然在任意時間點上它都包含某種特定的字符序列,但通過某些方法調用可以改變該序列的長度和內容。可將字符串緩沖區安全地用於多個線程。可以在必要時對這些方法進行同步,因此任意特定實例上的所有操作就好像是以串行順序發生的,該順序與所涉及的每個線程進行的方法調用順序一致”。StringBuilder是一個可變的字符序列,此類提供一個與StringBuffe兼容的API,但不保證同步。該類被設計用作StringBuffer的一個簡易替換,用在字符串緩沖區被單個線程使用的時候(這種情況很普遍)。如果可能,建議優先采用該類,因為在大多數實現中,它比StringBuffer要快。將StringBuilder的實例用於多個線程是不安全的,如果需要這樣的同步,則建議使用StringBuffer。
也就是說,一般推薦使用StringBuilder,這個不安全的家伙!!!
因為它快!!!
這里其實會有兩個明顯的疑問:
- 快多少?
- 會有靜態的StringBuilder么?
快多少?看這個:String、StringBuffer、StringBuilder的區別與效率比較
結論是量級小看不出,量級大還是有區別。
會有靜態的StringBuilder么?我沒想到...
所以,線程安全並不是說開發中一直提心吊膽考慮的問題;
簡單來說,有靜態變量了,多思考下應用場景,查一下前輩踩過的坑,問題基本避免了。
至於衍生問題:為什么StringBuilder比StringBuffer快?源碼告訴我們,后者有同步。