逐步理解Java中的線程安全問題


什么是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快?源碼告訴我們,后者有同步。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM