Synchronized涉及不少知識面,咱們先從一些相關知識講起。java
CAS 全稱 Compare And Swap (又稱Compare And Exchange) / 自旋 / 自旋鎖 / 無鎖程序員
由於常常配合循環操做,直到修改完成爲止,因此泛指一類操做算法
CAS操做雖然能夠作到不須要主動上鎖就能夠解決原子操做問題。可是其實在執行對比A與V的值時,在底層仍然是加鎖了的,否則沒法避免指令亂序執行而致使的數據不一致,只是這個操做對咱們程序員是無感知的。安全
這裏拓展一下,在CAS的底層實現中,(多核)CPU實際調用了指令 lock cmpxch。事實上也是經過上鎖來保證CAS操做的原子性,只不過這個鎖實際鎖定的是北橋信號而不是內存總線,效率較高些。多線程
具體能夠參考文章CAS你覺得你真的懂?app
ABA問題能夠理解爲,待修改對象值本來是A被修改成B後又改成了A,那麼此時再單純的對比對象值就沒辦法肯定是否應該更新了。jvm
解決辦法使用版本號來肯定修改版本(版本號 AtomicStampedReference),若是是基礎類型簡單值則不須要版本號。佈局
這個是操做系統中的一個概念。操做系統爲了不應用程序直接使用內核中的服務,把內核空間與用戶空間進行了分割。這也表明着,應用程序(如JVM)向操做系統申請內核服務或資源時,須要花費更多的時間與資源。優化
在早期JDK版本中實現synchronized,是經過向操做系統申請重量級鎖的方式實現的。而申請重量級鎖就必須經過操做系統的內核的容許再進行系統調用(以中斷的方式),如此一來,致使早期synchronied的實現特別低效。操作系統
注:*中斷和異常可使用系統調用來調用內核方法。
後來出現的偏向鎖和輕量級鎖(自旋鎖)在用戶態中處理,因此效率也高了不少。
講到Java的markword那就必需要講到Java對象的內存佈局了。
當咱們建立一個對象,對象信息會被jvm寫入內存,對象在內存中的分佈以下圖(如下是64位的jvm環境,默認開啓壓縮指針的狀況)
咱們稱markword+class pointer = 12Byte ,這12字節能夠稱做object header。它管理着一個對象的各類屬性狀態。object header後面的內容則是對象的成員變量。
JVM對內存的管理有8字節對齊的要求,因此咱們能夠看到一、3圖中都由於原始內容不足8*倍而進行了補齊填充。
可是他們的狀況有點不同,一個是在類成員後面補充,一個則是在class pointer和long類型成員間補充。因此對齊填充是有2種狀況的,這個也是我偶然發現的。。具體補齊的實現我沒有去查看細節,這個不做爲重點討論。
剛剛談到了咱們稱markword+class pointer = 12Byte ,這12字節能夠稱做object header。它管理着一個對象的各類屬性狀態。
如今具體來說講Markword中與鎖相關的位對應着什麼信息。
咱們直接看最後三位,首先,最後兩位決定了鎖的類型
0 1 :兩種可能
0 0 1:無鎖,新對象
1 0 1:偏向鎖
0 0 :自旋鎖
1 0 :重量級鎖
1 1 :GC標記
synchronized優化的過程和markword息息相關
markword上面已經看過了每一位的表示意義,如今來理解synchronized鎖升級的過程。
匿名偏向鎖
-XX:BiasedLockingStartupDelay會設定偏向鎖的啓動延時(默認4s),當JVM啓動時會有不少線程競爭,因此默認狀況啓動時不直接打開偏向鎖,再一段時間後設爲偏向鎖,此時爲匿名偏向鎖,線程ID爲0。
偏向鎖
偏向鎖特別的指向了某個線程,看markword-bit圖中能夠看到寫入了線程指針。(通常此時毫無競爭,哪一個線程來使用這把鎖,鎖就指向哪一個線程)
輕量級鎖
又稱自旋鎖,偏向鎖在輕度競爭時會撤銷偏向鎖,升級輕量級鎖,也有可能從普通對象直接升級爲輕量級鎖。若是產生了競爭,競爭的線程們會生成一個Lock Record於本身的線程棧中,並用CAS操做將markword設置爲指向本身線程的LockRecord的指針,設置成功者獲得鎖。其他沒有獲取到鎖的線程繼續自旋。
重量級鎖
競爭加重,有線程超過10次自旋,( 由-XX:PreBlockSpin控制自旋次數,默認爲10), 或者自旋線程數超過CPU核數的一半,升級重量級鎖,向操做系統申請資源, CPU從3級到0級系統調用,線程掛起,進入等待隊列,等待操做系統的調度,而後再映射回用戶空間(JAVA1.6以後,加入自適應自旋, JVM本身控制鎖升級)
爲何有自旋鎖還須要重量級鎖?
自旋是消耗CPU資源的,若是鎖的時間長,或者自旋線程多,CPU資源會被大量消耗
重量級鎖(ObjectMonitor)有等待隊列(WaitSet),全部拿不到鎖的進入等待隊列,不須要消耗CPU資源
偏向鎖是否必定比自旋鎖效率高?
不必定,在明確知道會有多線程競爭的狀況下,偏向鎖確定會涉及鎖撤銷,這時候直接使用自旋鎖
JVM啓動過程,會有不少線程競爭(明確),因此默認狀況啓動時不打開偏向鎖,過一段兒時間再打開
若是計算過對象的hashCode,則對象沒法進入偏向狀態!
輕量級鎖重量級鎖的hashCode存在與什麼地方?
答案:線程棧中,輕量級鎖的LockRecord中,或是表明重量級鎖的ObjectMonitor的成員中
可重入鎖,它的可重入性表如今同一個線程能夠屢次得到鎖。
首先synchronized就是可重入鎖,也必須是可重入鎖。不然子類調用父類的super.m()的操做就沒法實現。
並且重入的次數必須被記錄,由於解鎖須要對應的重入次數。
在高爭用 高耗時的環境下synchronized效率更高 在低爭用 低耗時的環境下CAS效率更高 synchronized到重量級以後是等待隊列(不消耗CPU) CAS(等待期間消耗CPU)
public void add(String str1,String str2){ StringBuffer sb = new StringBuffer(); sb.append(str1).append(str2); }
咱們都知道 StringBuffer 是線程安全的,由於它的關鍵方法都是被 synchronized 修飾過的,但咱們看上面這段代碼,咱們會發現,sb 這個引用只會在 add 方法中使用,不可能被其它線程引用(由於是局部變量,棧私有),所以 sb 是不可能共享的資源,JVM 會自動消除 StringBuffer 對象內部的鎖。
public String test(String str){ int i = 0; StringBuffer sb = new StringBuffer(): while(i < 100){ sb.append(str); i++; } return sb.toString(): }
JVM 會檢測到這樣一連串的操做都對同一個對象加鎖(while 循環內 100 次執行 append,沒有鎖粗化的就要進行 100 次加鎖/解鎖),此時 JVM 就會將加鎖的範圍粗化到這一連串的操做的外部(好比 while 體外),使得這一連串操做只須要加一次鎖便可。