當多個處理器同時處理的時候,一般須要處理互斥的問題。
通常的解決方式都會包含acquire
和release
這個兩種操做,操做保證,一個線程在acquire執行以後,在它執行release以前,其它線程不能完成acquire操做。這個過程常常就涉及到鎖。研究代表(L. Lamport A fast mutual execlusion algorithm),經過 fast locks算法能夠作到,lock和unlock操做所需的時間與潛在的競爭處理器數無關。
java內置了monitor來處理多線程競爭的狀況.java
一種優化方式是使用 輕量鎖來在大多數狀況下避免重量鎖的使用,輕量鎖的主要機制是在monitor entry的時候使用原子操做,某些退出操做也是這樣,若是有競爭發生就轉而退避到使用操做系統的互斥量算法
輕量鎖認爲大多數狀況下都不會產生競爭
在鎖的使用中通常會使用幾種原子指令: - CAS:檢查給定指針位置的值和傳入的值是否一致,若是一致,就修改 - SWAP:替換指針原位置的值,並返回舊的值 - membar:內存屏障約束了處理器在處理指令時的重排序狀況,好比禁止同讀操做被重排序到寫操做以後 Java中使用 two-word 對象頭 1. 是 mark word,它包括同步信息,垃圾回收信息、hash code信息 2. 指向對象的指針對象 這些指令的花銷很昂貴,由於他們的實現一般會耗盡處理器的重排序緩衝區,從而限制了處理器本來可以像流水線同樣處理指令的能力。研究數據發現(Eliminating_synchronization-related_atomic_operations_with_biased_locking_and_bulk_rebiasing)原子操做在真實的應用中,好比javac ,會致使性能降低20%。 > [此處2006年的文章第4段](https://blogs.oracle.com/dave/biased-locking-in-hotspot)大概說CAS和fence在操做系統中是序列化處理的,而序列化指令會使CPU幾乎中止,終止並禁止任何無需指令,並等待本地存儲耗盡。在多核處理器上,這種處理會致使至關大的性能損失
線程指針是NULL(0)表示當前沒有線程被偏向這個對象
當分配一個對象而且這個對象可以執行偏向的時候而且尚未偏向時,會執行CAS是的當前線程ID放入到mark word的線程ID區域。安全
若是成功,對象自己就會被偏向到當前線程,當前線程會成爲偏向全部者數據結構
線程ID直接指向JVM內部表示的線程;java虛擬機中則是在最後3bit填充0x5表示偏向模式。
若是CAS失敗了,即另外一個線程已經成爲偏向的全部者,這意味着這個線程的偏向必須撤銷。對象的狀態會變成輕量鎖的模式,爲了達到這一點,嘗試把對象偏向於本身的線程必須可以操做偏向全部者的棧,爲此須要全局安全點已經觸達(沒有線程在執行字節碼)。此時偏向擁有者會像輕量級鎖操做那樣,它的堆棧會填入鎖記錄,而後對象自己的mark word會被更新成指向棧上最老的鎖記錄,而後線程自己在安全點的阻塞會被釋放多線程
若是沒有被原有的偏向鎖持有者持有,會撤銷對象從新回到可偏向可是尚未偏向的狀態,而後嘗試從新獲取鎖。若是對象當前鎖住了是進入輕量鎖,若是沒有鎖住是進入未被鎖定的,不可偏向對象
下一個獲取鎖的操做會與檢測對象的mark word,若是對象是可偏向的,而且偏向的全部者是當前那線程,會沒有任何額外操做而立馬獲取鎖。oracle
這個時候偏向鎖的持有者的棧不會初始化鎖記錄,由於對象偏向的時候,是永遠不會檢驗鎖記錄的
unlock的時候,會測試mark word的狀態,看是否仍然有偏向模式。若是有,就不會再作其它的測試,甚至不須要管線程ID是否是當前線程IDapp
這裏經過解釋器的保證monitorexit操做只會在當前線程執行,因此這也是一個不須要檢查的理由
經驗發現爲特定的數據結構選擇性的禁用偏向鎖(Store-fremm biased lock SFBL)來避免不合適的狀況是合理的。爲此須要考慮每一個數據結構究竟是執行撤銷偏向的消耗小仍是從新回到可偏向的狀態消耗下。一種啓發式的方式來決定究竟是執行那種方式,在每一個類的元數據裏面都會包含一個counter和時間戳,每次偏向鎖的實例執行一次偏向撤銷,都會自增,時間戳用於記錄上次執行bulk rebias的時間。post
撤銷計數並統計那些處於可偏向可是未偏向狀態的撤銷,這些操做的撤銷只須要一次CAS就能夠
counter自己有兩個閾值,一個是bulk rebias閾值,一個是bulk revocation。剛開始的時候,這種啓發式的算法能夠單獨的決定執行rebias仍是revoke,一單bulk rebias的閾值達到,就會執行bulk rebias,轉移到 rebiasable狀態
time閾值用來重置撤銷的計數counter,若是自從上次執行bulk bias已經超過了這個閾值時間,就會發生counter的重置。性能
這意味着從上次執行bulk rebias到如今並無執行屢次的撤銷操做,也就是說執行bias仍然是個不錯的選擇
可是若是在執行了bulk rebias以後,在時間閾值以內,仍然一直有撤銷數量增加,一旦達到了bulk revocation的閾值,就會執行bulk revocation,此時這個類的對象不會再被容許使用偏向鎖。測試
Hotspot中的閾值以下 Bulk rebias threshold 20 Bulk revoke threshold 40 Decay time 25s撤銷偏向自己是一個消耗很大的事情,由於它必須掛起線程,遍歷棧找到並修改lock records(鎖記錄)
最明顯的查找某個數據結構的全部對象實例的方式就是遍歷堆,這種方式在堆比較小的時候還能夠,可是堆變大就顯得性能很差。爲類解決這個爲題,使用 epoch
。
epoch是一個時間戳,用來代表偏向的合法性,只要這個數據接口是可偏向的,那麼就會在mark word上有一個對應的epoch bit位
這個時候,一個對象被認爲已經偏向了線程T必須知足兩個條件,1: mark word中偏向全部這的標記必須是這個線程,2:實例的epoch必須是和數據結構的epoch相等
epoch自己的大小是限制的,也就是有可能出現循環,但這並不影響方案的正確性
經過這種方式,類C的bulk rebiasing操做會少去不少的花銷。具體操做以下
這樣就不用掃描堆了,對於那些沒有被改變epoch的實例(和類的epoch不一樣),會被自動當作可偏向可是尚未偏向的狀態
這種狀態可看作 rebiaseable
批量撤銷自己存在着性能問題,通常的解決方式以下
容許鎖具備永遠改變(或者不多)的固定偏向線程,而且容許非偏向線程獲取鎖而不是撤銷鎖。
這種方式必須確保獲取鎖的線程必須確保進去臨界區以前沒有其它線程持有鎖,而且不能使用 read-modify-write的指令,只能使用read和write
當前Hotspot JVM中的在32位和64位有不一樣的形式
64bit爲
32bit爲
輕量鎖(thin locks),細節如前所述。它在HotSpot中使用displaced header的方式實現,又被稱做棧鎖
mark完整的狀態轉換關係以下
此時有新線程來競爭
此時有新的線程來競爭,一種策略是使用啓發式的方式來統計撤銷的次數
對於通過bulk rebias的對象,檢查期間沒有鎖定的實例,它的epoch會和class的不同,變成過時,可是能夠偏向
5.1 若是 發生垃圾回收,lock會被初始化成可偏向但未偏向的狀態(這也能夠下降epoch循環使用的影響)
處於輕量鎖狀態,它可能沒有hashcode計算,可能有,這依賴於inflat
處於重量鎖狀態
計算過hashcode,再加鎖和解鎖對應狀態轉換(9.10)
Eliminating_synchronization-related_atomic_operations_with_biased_locking_and_bulk_rebiasing
Evaluating and improving biased locking in the HotSpot virtual machine
biased-locking-in-hotspot