1. 程式人生 > 遊戲 >《黑相集:心魔》封面公開 斧頭男背影殺氣側漏

《黑相集:心魔》封面公開 斧頭男背影殺氣側漏

併發與並行

  • 併發:同一時間只能處理一個任務,但是可以每個任務輪著做(時間片輪轉)
  • 並行:同一時間可以做多個任務

鎖機制

  • synchronized關鍵字:用於java物件、方法、程式碼塊提供執行緒安全的操作。java每個物件都有個monitor物件,加鎖就是在競爭monitor物件。 對程式碼塊加鎖是通過在前後分別加上monitorenter 以及 monitorexit(有兩個) 指令,正常情況下只會執行第一個monitorexit釋放鎖,在釋放鎖後就接著同步程式碼塊之後的內容繼續向下執行。第二個是用來處理異常的,如果程式發生異常,就會執行第二個monitorexiit,並且會繼續向下通過athrow 指令丟擲異常。對方法是否加鎖通過ACC_SYNCHRONIZED標記位來判斷。
    作用於成員變數和靜態方法是鎖住的this物件例項;作用於靜態方法,鎖住的是class例項;作用於程式碼塊時候,鎖住的所有程式碼塊中的物件。
    實際上 synchronized使用鎖是就是儲存在java物件頭中,物件是儲存在堆記憶體中的,每個物件內部都有一部分空間用於儲存物件頭資訊,物件頭資訊中包含了mark work用於存放hashcode 和物件的鎖資訊,不同狀態下儲存的資料結構有一些不同。

在JDK6以前,synchronized一直被稱為重量級鎖,monitor依賴於底層作業系統的lock實現,java的執行緒是對映到作業系統的原生執行緒上的

輕量級鎖:

在即將開始執行同步程式碼塊中的內容時候,會首先檢查物件的mark word,檢視鎖物件是否被其它執行緒佔用,如果沒有任何執行緒佔用,那麼會在當前的執行緒中所處的棧幀中建立一個名為鎖記錄(lock record)的空間,用於複製並存儲物件目前的mark word資訊。接著虛擬機器使用CAS操作將物件的mark word更新為輕量級鎖狀態(資料結構變為指向lock record的指標,指向當前的棧幀),CAS操作基於cmpxchg指令,如果CAS操作失敗的話,那麼說明有執行緒已經進入了這個同步程式碼塊中,這時候虛擬機器會再檢查物件的mark word 是否指向當前執行緒的棧幀,如果是說明不是其他執行緒,而是當前執行緒已經有了這個物件的鎖,如果不是,說明已經被其它執行緒佔用,只能將鎖膨脹為重量級鎖,按照重量級鎖操作執行。

重量級鎖:

在java虛擬機器中,monitor是由Objectmonitor實現的,每個等待鎖的執行緒都會被封裝成ObjectWaiter物件 ,

  • 首先會進入entry set 等著,當執行緒獲取到物件的monitor之後會進入the owner區域並把monitor中的owner變數設定為當前執行緒,同時monitor中的計數器count加一,
  • 若執行緒呼叫wait()方法,將釋放當前持有的monitor物件,owner變數恢復為null,count-1,同時該執行緒進入waitset集合中等待被喚醒,
  • 若當前執行緒執行完畢後也將釋放monitor並復位變數的值,以便其它的執行緒進入獲取物件的monitor。

自旋鎖

入自旋鎖之後,不會將處於等待狀態的執行緒掛起,而是通過無線迴圈的方式,不斷檢測是否能夠獲取鎖,由於單個執行緒佔用鎖的時間非常短,所以說迴圈的次數不會太多。如果等待時間太長,也只會浪費處理器資源,因此自旋鎖等待時間是有限制的,如果失敗就會採用重量級鎖機制。JDK1.6之後,自旋鎖得到了優化,次數限制不在是固定的,而是自適用變化的,如果某個鎖經常都自自旋失敗,有可能不在採用自旋策略,直接使用重量級鎖。
解鎖過程同樣採用CAS演算法,如果物件的mark word仍然指向執行緒的鎖記錄,那麼就用CAS操作把物件的mark word 和複製到棧幀中的displaced mark word 進行交換,如果替換失敗,說明其它執行緒嘗試獲取該鎖,在釋放鎖的同時,需要喚醒被掛起的執行緒。

偏向鎖

偏向鎖實際上專門為單個執行緒而生的,當某個執行緒第一次獲得鎖的時候,如果接下來都沒有其它執行緒來獲取此鎖,那麼持有鎖的執行緒不在需要同步操作,偏向鎖也會通過CAS操作記錄執行緒id,值得注意的是如果物件通過呼叫hashcode方法計算過物件的一致性hash值,那麼它不支援偏向鎖,會直接進入到輕量級鎖狀態,因為hash需要被儲存,而偏向鎖的mark word 無法儲存hash值,如果物件已經是偏向鎖狀態再呼叫hashcode方法,直接回將鎖升級為重量級鎖。

JMM記憶體模型

在CPU中,一般會有快取記憶體,為了解決記憶體的速度和處理器的速度,在CPU內部新增一級或者多級快取記憶體來提高處理器的資料獲取效率,但是現在都是基於多核處理器,每個處理器都有自己的快取記憶體,如何保證每個處理器的快取記憶體內容一致? 為了解決快取一致性的問題,需要各個處理器訪問快取時遵循一些協議,在讀寫時候根據協議來進行操作,java也採用了類似的模型來實現支援多執行緒的記憶體模型。 JMM記憶體模型規定如下:

  • 所有的變數全部儲存在主記憶體,包括成員變數靜態變數,不包括執行緒私有的區域性變數。
  • 每條執行緒都有自己的工作記憶體,執行緒對變數的所有操作必須在工作記憶體中進行, 不能直接操作主記憶體的資料
  • 不同執行緒之間的工作記憶體是相互隔離的,如果需要執行緒之間傳遞內容,只能通過主記憶體完成,無法直接訪問對方的工作記憶體
    每一條執行緒如果要操作主記憶體的資料,先得拷貝到自己的工作記憶體,並對工作記憶體中的資料的副本進行操作(load save),在將結果拷貝到主記憶體中。

重排序

在編譯或者執行時,為了優化程式的執行效率,編譯器或者處理器統稱會對指令進行重排序,有以下情況:

  • 編譯器重排序: java編譯器通過對java程式碼語義的理解根據優化規則對程式碼指令進行重排序。
  • 機器指令級別的重排序:現代處理器能夠自主的判斷和變更機器指令的執行順序。

volatile 關鍵字

當寫一個volatile變數時候,JMM會把該執行緒本地記憶體中的變數強制重新整理到主記憶體中去,並且這個寫操作會導致其他執行緒中的volatile變數快取無效,這樣另一個執行緒改變了這個值時,當前執行緒會立即得知,並將工作記憶體中變數更新為最新的版本。
volatile會禁止指令重拍,也就是說,如果我們操作的是一個volatile變數,將不會出現重排序,在編譯時,會在指令序列中插入記憶體屏障禁止特定型別的處理器重排序。如果在指令間插入一條memory barrier 則會告訴編譯器和CPU,不管社什麼指令都不能和memory barrier指令重排。所以volatile能夠保證之前的指令一定全部執行,之後的指令一定都沒有執行,並且前面的語句的結果對後面的語句可見
記憶體屏障:又稱記憶體柵欄,是一個CPU指令,有兩個作用:保證特定的操作順序;保證某些變數的記憶體可見性。

happens-before 原則

JMM 提出先行發生原則,禁止編譯優化的場景,來向各位程式設計師做一些保證,

  • 程式次序規則:
  • 監視器鎖規則:
  • volatile變數規則:
  • 執行緒啟動規則:
  • 執行緒加入規則:
  • 傳遞性規則