volatile是輕量級的synchronize,它在多處理器開發中保證了共享變量的「可見性」,由於它不會引發線程上下文的切換和調度,因此比synchronize的使用和執行成本更底。
爲了提升處理速度,處理器不直接和內存進行通訊,而是先將系統內存的數據讀到內部緩存(L1,L2或其餘)後再進行操做,但操做完不知道什麼時候會寫到內存。使用volatile變量,在操做後,JVM會發出lock指令java
synchronize實現同步的基礎,具體表現爲三種形式數組
當一個線程試圖訪問同步代碼塊時,它首先必須獲得鎖,退出或拋出異常時必須釋放鎖。那麼鎖到底存在那裏,鎖裏會存儲什麼信息。緩存
synchonize用的鎖是存在java對象頭裏的。若是對象是數組類型,則JVM用三個字寬存儲對象頭,若是對象爲非數組類型,則用二個字寬存儲對象頭。32位中,一字寬等於四字節(32bit)多線程
長度 | 內容 | 說明 |
32/64bit | Mark Word | 存儲對象的hashCode或鎖信息等。 |
32/64bit | Class Metadata Address | 存儲到對象類型數據的指針 |
32/64bit | Array length | 數組的長度(若是當前對象是數組) |
在運行期間Mark Word裏存存儲的數據會隨着鎖標誌位的變化而變化。會成爲下面的一種
併發
爲了減小得到鎖與釋放鎖所帶來的性能消耗,引入「偏向鎖」和「輕量級鎖'.因此在java中存在四種狀態性能
它會隨着競爭狀況逐漸升級。鎖能夠升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖優化
Hotspot的做者通過以往的研究發現大多數狀況下鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓線程得到鎖的代價更低而引入了偏向鎖。當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID,之後該線程在進入和退出同步塊時不須要花費CAS操做來加鎖和解鎖。spa
流程圖中展現偏向鎖的獲取釋放以及升級至輕量鎖
操作系統
1.輕量級鎖加鎖:
線程在執行同步塊以前,JVM會先在當前線程的棧楨中建立用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中,官方稱爲Displaced Mark Word。而後線程嘗試使用CAS將對象頭中的Mark Word替換爲指向鎖記錄的指針。若是成功,當前線程得到鎖,若是失敗,表示其餘線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。線程
2.輕量級鎖解鎖
輕量級解鎖時,會使用原子的CAS操做來將Displaced Mark Word替換回到對象頭,若是成功,則表示沒有競爭發生。若是失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。下圖是兩個線程同時爭奪鎖,致使鎖膨脹的流程圖。
借用網上流程圖以下:
當竟爭存在時,若是線程能夠很快得到鎖,那麼能夠不在OS層掛起線程(線程切換平均消耗8K個時鐘週期),讓線程多作幾個空操做(自旋)
鎖 | 優勢 | 缺點 | 適用場景 |
偏向鎖 | 加鎖和解鎖不須要額外的消耗,和執行非同步方法比僅存在納秒級的差距。 | 若是線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。 | 適用於只有一個線程訪問同步塊場景。</td |
輕量級鎖 | 競爭的線程不會阻塞,提升了程序的響應速度。 | 若是始終得不到鎖競爭的線程使用自旋會消耗CPU | 追求響應時間。同步塊執行速度很是快。 |
重量級鎖 | 線程競爭不使用自旋,不會消耗CPU。 | 線程阻塞,響應時間緩慢。 | 追求吞吐量。同步塊執行速度較長。 |
內置於JVM中的獲取鎖的優化方法與獲取鎖的步驟