目錄
- synchronized基本使用
- synchronized底層原理
-
- Monitor
- Mark Word
- synchronized鎖優化
-
- 偏向鎖
- 輕量級鎖
- 自旋鎖
- 适應性自旋鎖
- 鎖消除
- 鎖粗化
synchronized基本使用
synchronized可以作用在方法、靜态方法、類、代碼塊上
一、修飾方法,修飾方法時鎖的範圍是整個方法,鎖的對象是調用該方法的執行個體對象(不同執行個體對象互不影響),需要注意的是synchronized不能被子類重寫的方法繼承
public synchronized void method() {
//TODO
}
二、修飾一個代碼塊,如果代碼塊包住了整個方法的代碼則等價于修飾了該方法,修飾代碼塊時必須指定一個對象,這個對象可以是this也可以new一個對象,擷取鎖時是擷取了這個對象的鎖
public void method() {
synchronized(this) {
//TODO
}
}
Object obj = new Object();
public void method() {
synchronized(obj) {
//TODO
}
}
三、修飾一個靜态方法,修飾在靜态方法上時,鎖作用的對象是這個類的對象
public synchronized static void method() {
//TODO
}
四、修飾一個類,這種寫法和修飾靜态方法的效果是一樣的,都是鎖住了這個類的對象
class Demo {
public void method() {
synchronized(Demo.class) {
//TODO
}
}
}
synchronized底層原理
Monitor
synchronized基于對象的Monitor鎖(螢幕鎖)實作,每個對象都持有Monitor,線程通過Monitorenter指令進入Monitor持有鎖,通過Monitroexit指令退出Monitor對象釋放鎖,Monitor鎖底層是作業系統通過Mutex lock實作的,是以如果直接操作Monitor就會涉及到使用者态和核心态的轉換,非常消耗性能,是以synchronized被稱為重量級鎖,當然在Java1.5版本之後Oracle公司已經對synchronized做了大量優化,性能和ReentrantLock持平,反編譯位元組碼檔案後我們會發現當synchronized修飾在方法上時是使用一個辨別符ACC_SYNCHRONIZED來辨別目前方法是一個同步方法,而當synchronoized修飾代碼塊時使用了一次monitorenter指令和兩次monitorexit指令,第一次是同步正常退出釋放鎖,第二次是發生異步退出釋放鎖
Mark Word
每個對象都持有一個Monitor,那麼對象是如何記錄Monitro資訊的呢,答案就是Mark Word(對象頭),記憶體中的對象由三部分組成:對象頭、執行個體資料、對齊填充
- 對象頭:包含對象的hashcode、對象所屬的年代、鎖标記位、偏向鎖(線程)ID、偏向時間、數組長度等,對象頭一般占用2個機器碼(子寬),32位虛拟機中一個機器碼是4個位元組(32bit),64位虛拟機中一個機器碼是8個位元組(64bit),如果對象是個數組則會占用3個機器碼,其中一個機器碼用來記錄數組長度
- 執行個體資料:存放類的屬性資料資訊,包括父類的屬性資訊
- 對齊填充:虛拟機要求對象起始位址必須是8位元組的整數倍,填充資料隻是為了位元組對齊
為了存儲更多的資料,Mark Word被設計成一個非固定的結構,它會根據對象的狀态複用自己的存儲空間,也就是說Mark Word是可變的,變化的結構如圖:
synchronized鎖優化
synchronized鎖優化後有四種狀态,級别從低至高分别是:無鎖狀态、偏向鎖、輕量級鎖、重量級鎖,随着鎖競争的激烈,鎖的級别是逐漸上升的,叫做鎖的更新,鎖的更新是單向的隻能更新不能降級
偏向鎖
偏向鎖是JDK1.6後新引入的,研究發現在大多數情況下,鎖不僅不存在多線程競争,而且總是由一個線程多次擷取,是以為了減少線程每次擷取鎖的耗時而引入了偏向鎖,偏向鎖的核心思想是當線程擷取鎖之後,鎖進入偏向模式,當線程再次擷取鎖時無需CAS操作,進而減少消耗提高性能,偏向鎖适合單線程執行代碼塊的場合,在鎖競争激烈的場合則會更新為輕量級鎖或重量級鎖,偏向鎖預設是開啟的,可以通過參數-XX:-UseBiasedLocking禁止偏向鎖
輕量級鎖
偏向失敗後,JVM先嘗試升降到輕量級鎖,使用輕量級鎖是為了減少直接使用重量級鎖引起的開銷,輕量級鎖提升性能的依據是"對于絕大部分的鎖,在整個生命周期内是不會産生競争的",輕量級鎖适用于多個線程交替執行代碼塊的場合,如果線程之間的競争更加激烈,則從輕量級鎖膨脹為重量級鎖
自旋鎖
由于在大多數情況下線程持有鎖的時間不會太長,為了避免在短時間内阻塞和喚起線程,引入了自旋的概念,當一個線程嘗試擷取鎖時,如果該鎖已經被其他線程占據,則進入空循環等待鎖釋放,而非進入休眠,這個過程叫做自旋,當然自旋會持續占用CPU,如果鎖長時間不釋放,就會白白浪費性能,是以自旋的時機或者次數需要有個上限,如果長時間擷取不到鎖,則更新到重量級鎖
适應性自旋鎖
如果某次自旋成功了,那麼下次自旋的次數将會增加,反之如果自旋很少成功,則自旋次數會減少,也就是說JVM會根據上次自旋的情況調整自旋的次數
鎖消除
鎖消除指的是虛拟機在編譯時會去除不可能存在共享資源競争的鎖,節省無意義的加鎖時間,有點時候并非我們主動寫的同步代碼而是使用的api中具有同步方法比如stringBuff的append方法,鎖消除的依據是逃逸分析的資料支援,使用JVM參數可控制是否開啟逃逸分析,預設是開啟的,-XX:+DoEscapeAnalysis : 開啟逃逸分析 -XX:-DoEscapeAnalysis : 關閉逃逸分析
鎖粗化
鎖粗化也很好了解,實際在使用synchronized時,我們可能會隔一段代碼使用一個synchronized同步塊,這種情況下連續加鎖會造成額外的損耗,JVM會将一段一段的鎖合并成一個鎖