1. 程式人生 > >Java並發編程原理與實戰二十:線程安全性問題簡單總結

Java並發編程原理與實戰二十:線程安全性問題簡單總結

依次 mar 時間 clu 版本號 exc 虛擬 locking ron

一、出現線程安全性問題的條件 •在多線程的環境下 •必須有共享資源 •對共享資源進行非原子性操作 二、解決線程安全性問題的途徑 •synchronized (偏向鎖,輕量級鎖,重量級鎖) •volatile •JDK提供的原子類 •使用Lock(共享鎖,排它鎖) 三、認識的“*鎖” •偏向鎖 Java偏向鎖(Biased Locking)是Java6引入的一項多線程優化。
偏向鎖,顧名思義,它會偏向於第一個訪問鎖的線程,如果在運行過程中,同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發同步的,這種情況下,就會給線程加一個偏向鎖。
如果在運行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會被掛起,JVM會消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖。
•輕量級鎖 輕量級鎖是由偏向所升級來的,偏向鎖運行在一個線程進入同步塊的情況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級為輕量級鎖; •重量級鎖 重量級鎖,就是讓爭搶鎖的線程從用戶態轉換成內核態。讓cpu借助操作系統進行線程協調 •重入鎖 重進入是指任意線程在獲取到鎖之後,再次獲取該鎖而不會被該鎖所阻塞。關聯一個線程持有者+計數器,重入意味著鎖操作的顆粒度為“線程 •自旋鎖

自旋鎖原理非常簡單,如果持有鎖的線程能在很短時間內釋放鎖資源,那麽那些等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞掛起狀態,它們只需要等一等(自旋),等持有鎖的線程釋放鎖後即可立即獲取鎖,這樣就避免用戶線程和內核的切換的消耗。

但是線程自旋是需要消耗cup的,說白了就是讓cup在做無用功,如果一直獲取不到鎖,那線程也不能一直占用cup自旋做無用功,所以需要設定一個自旋等待的最大時間。

如果持有鎖的線程執行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導致其它爭用鎖的線程在最大等待時間內還是獲取不到鎖,這時爭用線程會停止自旋進入阻塞狀態。

•共享鎖 又稱讀鎖,若事務T對數據對象A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。 •獨占鎖 排他鎖(Exclusive Locks,簡稱X鎖),又稱為寫鎖、獨占鎖,是一種基本的鎖類型。
•排他鎖 排他鎖(Exclusive Locks,簡稱X鎖),又稱為寫鎖、獨占鎖,是一種基本的鎖類型。 •讀寫鎖 讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。 •公平鎖

公平和非公平鎖的隊列都基於鎖內部維護的一個雙向鏈表,表結點Node的值就是每一個請求當前鎖的線程。公平鎖則在於每次都是依次從隊首取值。

•非公平鎖

在等待鎖的過程中, 如果有任意新的線程妄圖獲取鎖,都是有很大的幾率直接獲取到鎖的。

•死鎖 死鎖:兩個或多個線程相互等待對方釋放鎖,則會出現死鎖現象。java虛擬機沒有檢測,也沒有采用措施來處理死鎖情況,所以多線程編程是應該采取措施避免死鎖的出現。一旦出現死鎖,整個程序即不會發生任何異常,也不會給出任何提示,只是所有線程都處於堵塞狀態。 活鎖 任務或者執行者沒有被阻塞,由於某些條件沒有滿足,導致一直重復嘗試,失敗,嘗試,失敗。活鎖和死鎖的區別在於,處於活鎖的實體是在不斷的改變狀態,所謂的“活”, 而處於死鎖的實體表現為等待;活鎖有可能自行解開,死鎖則不能。 •樂觀鎖

樂觀鎖是一種樂觀思想,即認為讀多寫少,遇到並發寫的可能性低,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,采取在寫時先讀出當前版本號,然後加鎖操作(比較跟上一次的版本號,如果一樣則更新),如果失敗則要重復讀-比較-寫的操作。
java中的樂觀鎖基本都是通過CAS操作實現的,CAS是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗。

•悲觀鎖 悲觀鎖是就是悲觀思想,即認為寫多,遇到並發寫的可能性高,每次去拿數據的時候都認為別人會修改,所以每次在讀寫數據的時候都會上鎖,這樣別人想讀寫這個數據就會block直到拿到鎖。java中的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嘗試cas樂觀鎖去獲取鎖,獲取不到,才會轉換為悲觀鎖,如RetreenLock。

Java並發編程原理與實戰二十:線程安全性問題簡單總結