Synchronized實現原理
一,前言
Synchronized 在多執行緒環境下是不可缺少的,那麼對於Synchronized 又瞭解多少呢。下面就係統總結,而對於Synchronized的基本使用,請參看另一篇部落格。
1.1,Synchronized 作用
- 確保執行緒互斥的訪問同步程式碼
- 保證共享變數的修改能夠及時可見
- 有效解決重排序問題
二,從JVM理解Synchronized
首先使用JDK自帶的反編譯工具檢視Synchronized編譯後的位元組碼,開啟cmd進入到.class檔案所在檔案目錄,輸入javap -v 類名.class
先看如下程式碼:
package com.mult; public class Demo { private static int value = 10; public static void main(String[] args) { System.out.println(new Demo().method()); } public synchronized int method() { synchronized (Demo.class) { if (value > 5) { return value; } else { return 0; } } } }
從上圖可以看出Synchronized 是通過monitorenter和monitorexit兩個位元組碼指令實現的。在每一個物件中都會存在一個Monitor監視器,而monienter和monitorexit兩者之間是互斥關係,monienter用於獲取物件鎖,而moniexit釋放物件鎖。
在JVM規範文件中有以下說明:
- 如果 Monitor 的計數器為 0,則該執行緒進入 Monitor,然後將計數器值設定為 1,該執行緒即為 Monitor 的所有者,也就是說此時獲取到物件鎖。
- 如果執行緒已經佔有該 Monitor,只是重新進入,則進入 Monitor 的計數器加 1。
- 如果其他執行緒已經佔用了 Monitor,則該執行緒進入阻塞狀態,直到 Monitor 的計數器為 0,再重新嘗試獲取 Monitor 的所有權。
當計數器為0時,Monitor便會釋放物件鎖,那麼其他阻塞的執行緒就可以嘗試申請獲取物件鎖。
總結這裡,就要引出另一個內容,就是Synchronized是可重入鎖。
三,可重入鎖
可重入鎖: 一個執行緒已經獲取到物件鎖時,其他執行緒處於阻塞狀態。但獲取到物件鎖的執行緒再次去請求自己所持有的物件鎖資源時,這種情況成為可重入鎖。
請看例項程式碼:
public class Demo { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { Demo demo = new Demo(); demo.method_1(); } }).start(); } public synchronized void method_1() { System.out.println(Thread.currentThread().getName()+"-->method_1...."); method_2(); } public synchronized void method_2() { System.out.println(Thread.currentThread().getName()+"-->method_2...."); } }
以上程式碼中只有一個demo物件鎖,在method_1中呼叫method_2結果依然可以列印,證明Synchronized是可重入鎖。反之,如果不是可重入鎖,那麼在method_1中獲取到物件鎖,接著呼叫method_2便會產生死鎖,另外兩個方法的執行緒名稱是相同的,也可以證明該執行緒拿到的就是同一個物件鎖。
注意:當子類繼承父類時,子類也是可以通過可重入鎖呼叫父類的同步方法。
四,鎖的優化
在JDK6之後,對Synchronized的實現進行了優化,引入了偏向鎖、輕量級鎖,鎖,它們之間的關係為;
無鎖->偏向鎖->輕量級鎖->重量級鎖
注意:以上級別之間的轉換是單向的,只能從低階轉向高階,反之不可。
4.1,偏向鎖
在某一環境下,一個執行緒可能會多次獲得物件鎖。那麼頻繁的申請鎖釋放鎖勢必會對效能造成一定影響,因此引入偏向鎖概念。當一個執行緒頻繁獲得物件鎖時,會在物件頭中儲存鎖偏向的執行緒ID,然後當該執行緒再次申請或釋放鎖時,就不再需要做其他的同步操作,因而在一定程度上可以提高系統性能。
4.2,輕量級鎖
輕量級鎖在偏向鎖的上一級,在偏向鎖不再適用的情況下,就會向上升級。當升級為輕量級鎖時,Mark Word的結構也會相應的變化。執行緒在棧幀中建立鎖記錄,接著將鎖物件中Mark Word複製到執行緒建立的所記錄中,而鎖物件中的Mark Word則被替換為指向鎖記錄的指標,完成輕量級鎖的實現。而輕量級鎖的引入是為解決在重量級鎖中,多執行緒之間的效能消耗問題。
4.3,自旋鎖
自選鎖顧名思義就是“自己旋轉”。同樣在多執行緒的環境下,其中一條執行緒獲得物件鎖,而其他的執行緒則在原地迴圈等待其他執行緒釋放鎖,而不是處於執行緒阻塞狀態。這種原地迴圈等待的情況是會消耗CPU資源的,預設情況下迴圈10次。自旋鎖的使用一般是小城獲取鎖的時間較短,讓其他執行緒稍微等待一段時間進而再獲得物件鎖,比如對於同步程式碼塊的執行一般是較快的。如果執行緒迴圈時間較長,那麼作業系統便會將此執行緒掛起,避免資源的更多浪費。
對於自旋的概念可能不太好理解,下面寫個小Demo。
public static void main(String[] args) {
// 執行緒1
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"開始執行了...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"執行完畢...");
}
}).start();
// 執行緒2
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"開始執行了...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"執行完畢...");
}
}).start();
System.out.println("全部執行緒執行完畢...");
}
執行結果:
分析:以上案例本來的目的是當全部執行緒執行完畢後,再列印全部執行緒執行完畢。但是在多執行緒情況下這是無法保證的,下面進行優化。
while(Thread.activeCount() != 1){
}
System.out.println("全部執行緒執行完畢...");
重複的程式碼就不再展示,只是在最後一句列印前新增死迴圈,讓其一直判斷當前活動的執行緒是否只剩下一個,如果是則退出while迴圈。那麼while迴圈就是一直在不停迴圈的等待過程,直到活動執行緒為最後一個。
適應性自旋
是不固定自旋10次一下。它可以根據它前面執行緒的自旋情況,從而調整它的自旋,甚至是不經過自旋而直接掛起。
4.4,重量級鎖
當輕量級鎖膨脹到重量級鎖之後,表示執行緒只能被掛起阻塞來等待被喚醒了,那麼這種鎖機制效率就相對比較慢,同時比較損耗系統資源。
五,總結
到這裡關於Synchronized的總結就結束了,還有一種ReentrantLock鎖也是可重入鎖。
以上內容均是學習總結,如有不適之處歡迎留言指正。
感謝閱讀