高效併發是jdk1.5到1.6的一個重要改進,hotspot虛擬機開發團隊在這個版本上花費了大量精力去實現各類鎖優化技術,例如:適應性自旋、鎖消除、鎖粗化、輕量級鎖跟偏向鎖等,這些技術都是爲了在線程間更高效的共享數據,以解決競爭問題,從而提升程序執行效率。
自旋鎖與自適應自旋:
虛擬機開發團隊注意到,在許多應用上共享數據的鎖定狀態只會持續很短的時間,爲了這段時間去掛起跟恢復線程並不值得(線程的內核態跟用戶態轉換消耗較高),若是物理機有一個以上的處理器,咱們可讓後面請求鎖的線程不要放棄cpu的執行時間,而是「稍等一下」,看看持有鎖的線程是否會很快釋放鎖;這個「等一下」通常會實現爲一個for循環(自旋),這就是所謂的自旋鎖。
但for循環的時候也會消耗cpu性能!!若是前一個線程很快釋放了鎖還好,若是執行較慢,在等待的線程一直自旋,對cpu也是一種很大的消耗,所以自旋要有條件限制,通常默認次數爲10次,自旋10次沒法獲取鎖,則掛起線程。由於等待時間不可控的狀況,jdk1.6引入了自適應的自旋鎖。就是jdk會根據本身等待的「經驗」來決定自旋時間;若是以前的等待都成功了,則認爲本次執行也極可能成功,等待時間會適當延長。
鎖消除:
虛擬機即時編譯器在運行的時候,會對一些代碼上要求同步,但被檢測到不可能存在同步的代碼進行鎖消除。鎖消除的主要判斷依據來源於逃逸分析的數據支持,若是判斷在一段代碼中,堆上的全部數據都不會逃逸出去從而被其它線程訪問到,那就能夠把它們當作棧上的數據對待,認爲他們是線程私有的,同步加鎖無需進行。
例如:
public String concatString(String s1, String s2, String s3){
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}
StringBuffer的append是同步的,但通過分析,咱們發現它的動態做用域被限制在concatString方法中,相關變量也都在線程執行的jvm棧中,其它線程沒法獲取,所以,雖然這裏有鎖,但能夠安全的消除掉,即時編譯器編譯以後,這段代碼就會忽略掉全部的同步而直接執行了。
鎖粗化:
寫代碼的時候,咱們老是要把同步的範圍設置的儘可能小,以使得須要同步的代碼量儘可能變小,執行速度快,而讓其它線程儘快拿到鎖。
但若是一系列的操做都是對同一個對象反覆加鎖跟解鎖,甚至加鎖操做在一個循環中,會致使即便沒有競爭,也會由於頻繁加解鎖而產生沒必要要的性能損耗。例如上例的concatString方法有三個append都是對同一對象加鎖,其實只須要一次就夠了;若是jvm探測到這樣的代碼,會把加鎖同步範圍擴展(粗化)到真個操做序列的外部(固然,只是舉例說明,實際這裏最終會由於沒有競爭而被消除鎖)。
輕量級鎖:
輕量級鎖不是用來代替重量級鎖的,它的本意實在沒有多餘線程競爭的前提下,減小傳統的重量級鎖由於使用操做系統互斥量而產生的性能消耗。
jvm中java對象的對象頭包含4部分:hashCode,gc分代,鎖標誌位,固定位0。
輕量級鎖就是採用一種樂觀鎖的機制,對對象的鎖標誌位進行「搶佔」,成功,則將鎖標誌位設置爲一個指向線程執行棧的指針(所指向的棧幀存放原來鎖標誌位的內容),失敗則檢查是否當前線程持有該對象鎖,是則繼續執行,不然說明發生了多線程鎖競爭,升級爲正常的重量級鎖。
輕量級鎖採用cas操做,避免了互斥量的開銷,相對來講比較輕便,但若是競爭較爲頻繁的話,輕量級鎖不但升級爲了普通的重量級鎖,並且還額外多了cas的操做,會致使輕量級鎖比重量級鎖更慢。
偏向鎖:
偏向鎖的目的是消除代碼在無競爭的狀況下的同步操做,進一步提升程序的性能。若是說輕量級鎖是在無競爭的狀況下使用cas操做去代替互斥量,那偏向鎖就是在無競爭的狀況下,乾脆把整個同步都消除掉,連cas操做都不做了。
偏向鎖的「偏」,就是偏愛的偏,意思是這個鎖會偏向於第一個得到它的線程,若是在接下來的執行過程當中該鎖沒有被其它線程獲取,則持有偏向鎖的線程將永遠不須要再進行同步。實現原理跟輕量級鎖同樣,也是經過修改對象頭信息中的鎖標誌位進行的。
當有另外一個線程嘗試獲取這個鎖時,偏向模式就宣告結束,根據鎖對象目前是否處於被鎖定狀態,撤銷偏向後恢復到未鎖定狀態,或者輕量級鎖定狀態。同輕量級鎖同樣,偏向鎖也是一個帶有效益權衡性質的優化,若是程序中大多數的鎖老是存在多個線程的競爭訪問,那偏向鎖就是多餘的,這時候禁用掉偏向反而會提高性能。