避免活躍性危險(第十章)

避免活躍性危險

在安全性與活躍性之間一般存在着某種制衡,咱們使用加鎖機制來確保線程安全,但若是過分地使用加鎖,則可能致使「鎖順序死鎖」。一樣,咱們使用線程池和信號量來限制對資源的使用,但這些被限制的行爲可能會致使資源死鎖。安全

1. 死鎖

  • 鎖順序死鎖:兩個線程試圖以不一樣的順序來得到相同的鎖,若是按照相同的順序來請求鎖,那麼就不會出現循環的加鎖依賴性,所以也就不會產生死鎖。 在制定鎖的順序時,可使用System.identityHashCode方法,該方法返回有Object.hashCode返回的值,經過比較大小等方法定義鎖的順序。在某些狀況下,兩個對象可能擁有相同的散列值,此時必須經過某種方法來決定鎖的順序,而這可能會從新引入死鎖,爲了不這種狀況,可使用「加時賽」鎖,在得到兩個對象的鎖以前,首先得到這個加時賽鎖,從而保證每次只有一個線程以未知的順序得到這兩個鎖。併發

  • 在協做對象之間發生的死鎖 若是在持有鎖的狀況下調用某個外部方法,那麼就須要警戒在協做對象之間發生死鎖。 若是在持有鎖時調用某個外部方法,那麼將出現活躍性問題,在這個外部方法中可能會得到其餘鎖(這可能會產生死鎖),或者阻塞時間過長,致使其餘線程沒法及時得到當前被持有的鎖。ide

  • 開放調用 若是在調用某個方法時不須要持有鎖,那麼這種調用被稱爲開放調用。 在程序中應儘可能使用開放調用。與那些在持有鎖時調用外部方法的程序相比,更易於對依賴於開放調用的程序進行死鎖分析。線程

  • 資源死鎖設計

  1. 線程飢餓死鎖。若是某些任務須要等待其餘任務的結果,那麼這些任務每每是產生線程飢餓死鎖的主要來源。
  2. 有界線程池/資源池與相互依賴的任務不能一塊兒使用

2. 死鎖的避免與診斷

  1. 若是一個程序每次至多隻能得到一個鎖,那麼就不會產生鎖順序死鎖。
  2. 若是必須獲取多個鎖,那麼在設計時必須考慮鎖的順序:儘可能減小潛在的加鎖交互數量,將獲取鎖時須要遵循的協議寫入正式文檔並始終遵循這些協議。
  3. 在使用細粒度鎖的程序中,能夠經過使用一種兩階段策略來檢查代碼中的死鎖:首先,找出在什麼地方將獲取多個鎖,而後對全部這些實例進行全局分析,從而確保它們在整個程序中獲取鎖的順序都保持一致。
  4. 使用顯示鎖Lock類中的定時tryLock功能,在等待超過指定時間後tryLock會放回一個失敗信息。

3. 其餘活躍性危險

  1. 飢餓:當線程因爲沒法訪問它所須要的資源而不能繼續執行時,就發生了「飢餓」。

引起飢餓的最多見資源就是CPU時鐘週期,若是在Java應用程序中對線程的優先級使用不當,或者在持有鎖時執行一些沒法結束的結構(例如無限循環,或者無限制地等待某個資源),那麼也可能致使飢餓,由於其餘須要這個鎖的線程將沒法獲得它。線程優先級並非一種直觀的機制,而經過修改線程優先級所帶來的效果一般也不明顯。當提升某個線程的優先級時,可能不會起到任何做用,或者也可能使得某個線程的調度優先級高於其餘線程,從而致使飢餓。code

一般,咱們儘可能不要改變線程的優先級。只要改變了線程的優先級,程序的行爲就將與平臺相關,而且會致使發生飢餓問題的風險。
    Thread.yield以及Thread.sleep的語義都是UB,JVM既能夠將他們實現爲空操做,也能夠將它們視爲線程調度的參考。
  1. 活鎖(Livelock)

活鎖是另外一種形式的活躍性問題,該問題不會致使線程阻塞,但也不能繼續執行。由於線程將不斷重複執行相同的操做,並且總會失敗。 活鎖一般發生在處理事務消息的應用程序中:若是不能成功處理某個消息,那麼消息處理機制將回滾整個事務,並將它從新放到隊列的開頭。當多個相互協做的線程都對彼此進行響應從而修改各自的狀態,並使得任何一個線程沒法繼續執行時,就發生了活鎖,在併發應用程序中,經過等待隨機長度的時間和回退能夠有效避免活鎖的發生。對象

相關文章
相關標籤/搜索