要理解Synchronized,首先要清楚偏向鎖,輕量級鎖和重量級鎖,在使用方面須要有wait/wait(time)/notify/notifyAll等,下面咱們就來介紹一下Synchronized的流程和使用方法;java
(Java SE 1.6中爲了減小得到鎖和釋放鎖帶來的 性能消耗而引入的偏向鎖和輕量級鎖)git
Synchronized的升級順序是 無鎖-->偏向鎖-->輕量級鎖-->重量級鎖,順內不可逆。github
當一個線程訪問同步代碼塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID,偏向鎖是一個可重入的鎖,之後該線程在進入和退出該同步代碼塊時不須要花費 CAS 操做來加鎖和解鎖,而只需簡單的測試一下對象頭的 Mark Word 裏是否存儲着指向當前線程的偏向鎖(當前線程的線程ID),若是測試成功,表示線程已經得到了鎖,若是測試失敗,則須要再測試下 Mark Word 中偏向鎖的標識是否設置成 1(表示當前是偏向鎖),若是偏向鎖標識是1,則使用 CAS 進行鎖獲取,偏向鎖標識不是1,則嘗試使用 CAS 將對象頭的偏向鎖指向當前線程,上述兩種CAS獲取鎖的操做,若是CAS操做成功則獲取到了偏向鎖,失敗則表明出現了鎖競爭,須要鎖撤銷操做。安全
偏向鎖使用了一種等到競爭出現才釋放鎖的機制,因此當其餘線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖。偏向鎖的撤銷須要等待擁有偏向鎖的線程到達全局安全點(在這個時間點上沒有字節碼正在執行),會首先暫停擁有偏向鎖的線程,而後檢查持有偏向鎖的線程是否活着,若是線程不處於活動狀態,則將鎖的對象的對象頭設置成無鎖狀態,若是線程仍然活着,擁有偏向鎖的棧會被執行**(判斷是否須要持有鎖),遍歷偏向對象的鎖記錄,查看使用狀況,若是還須要持有偏向鎖,則偏向鎖升級爲輕量級鎖**,若是不須要持有偏向鎖了,則將鎖對象恢復成無鎖狀態,最後喚醒暫停的線程。ide
線程在執行同步塊以前,JVM 會先在當前線程的棧楨中建立用於存儲鎖記錄的空間,並將對象頭中的 Mark Word 複製到鎖記錄中,官方稱爲 Displaced Mark Word。而後線程嘗試使用 CAS 將對象頭中的 Mark Word 替換爲指向鎖記錄的指針。若是成功,當前線程得到鎖,若是失敗,表示其餘線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖,自旋有必定次數,若是超過設置自旋的次數則升級到重量級鎖,或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖升級爲重量級鎖,重量級鎖使除了擁有鎖的線程之外的線程都阻塞,防止CPU空轉。性能
輕量級解鎖時,會使用原子的 CAS 操做來將 Displaced Mark Word 替換回到對象頭,若是成功,則表示沒有競爭發生。若是失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。測試
**例如:**T1線程持有鎖,T2線程自旋,可是T2線程自旋最大次數已通過了,則自旋失敗,進行鎖升級到重量級鎖,T2線程阻塞,這時T1執行完了同步代碼塊,進行輕量級鎖解鎖,可是這時Mark Word中的標誌位已經從原來的00(偏向鎖)變成了10(中練級鎖),解鎖會CAS失敗,T1會進行解鎖(釋放監視器,釋放鎖),並喚醒線程T2.ui
Synchronized是非公平鎖,Synchronized在線程進入阻塞隊列時,等待的線程會先嚐試獲取鎖,若是獲取不到就進入阻塞隊列,這明顯對於已經進入隊列的線程是不公平的。spa
鎖 | 優勢 | 缺點 | 場景 |
---|---|---|---|
偏向鎖 | 加解鎖不須要過多的資源消耗,和非同步方法的相比僅僅是納秒的差距 | 若是存在所競爭,會有額外的鎖撤銷操做 | 適用於只有一個線程訪問的場景 |
輕量級鎖 | 競爭線程不會阻塞,會自旋,減小了上線文切換。 | 若是始終得不到鎖,會消耗cpu資源 | 追求響應時間,同步代碼塊多爲計算,執行快的場景 |
重量級鎖 | 沒啥優勢 | 線程阻塞響應時間慢 | 同步代碼塊執行時間較長的場景使用 |
1: Synchronized 是java的內置鎖,也是排它鎖和非公平鎖,排它鎖也就是當前線程獲取鎖後,其餘線程都會阻塞掛起 ,非公平鎖是在線程後去鎖的時候會先嚐試後去鎖,獲取不到在進行阻塞。線程
2: Synchronized 是如何保證 '原子性' 的?是由於進入 Synchronized 塊的內存語義是把 Synchronized 塊內使用的 '工做內存清除', 這樣在使用共享變量時就會直接存主內存中獲取並複製到工做你內存中,在退出 Synchronized 語句塊時 會把 工做內存中計算過的 '共享變量' 更新到主內存中。
3: 獲取到 Synchronized 鎖 ,都是 '對象鎖'而非'代碼塊鎖' (鎖的都是對象或類,而不是某個方法),所以 Synchronized 是具備可重入性,在獲取到該對象鎖後能夠不用再次獲取該對象其餘方法的鎖,直接進入。
4: 若是是 Synchronized 用在 static 上, 就表明是類鎖(.class),不管建立多少個對象都不可行;
wait和sleep區別在於wait會釋放鎖, 可是sleep不會釋放鎖 ,sleep會致使線程阻塞掛起。
wait/wait(timeout)/notify/notifyAll 方法僅能夠在獲取到鎖後纔可使用。
wait: 線程等待。
wait(time): 線程等待,若是時間超過了設置的time,則繼續執行。
notify: 隨機喚醒一個等待的線程。
notifyAll: 喚醒所有等待線程。
/** * @Auther: concurrenncy * @Date: 2019-03-25 16:43 * @Company: 隨行付支付有限公司 * @maill: lan_tao@suixingpay.com * @Description: wait 和 sleep 區別在於 wait會釋放鎖, 可是 sleep 不會 ,sleep會致使線程阻塞掛起 */
public class WaitAndNotifyTest {
private static Object obj = new Object();
public static void main(String[] args) {
// 建立線程 thread1
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " begin wait...");
synchronized (obj) {
obj.wait();
}
System.out.println(Thread.currentThread().getName() + " end wait...");
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread1");
// 建立線程 thread2
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " begin wait...");
synchronized (obj) {
obj.wait();
}
System.out.println(Thread.currentThread().getName() + " end wait...");
} catch (Exception e) {
e.printStackTrace();
}
}
}, "thread2");
// 啓動
thread1.start();
thread2.start();
try {
// 睡眠一秒
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 若是調用 notify 的線程未獲取 對象鎖,在調用 notify 的時候會拋出 java.lang.IllegalMonitorStateException 異常
synchronized (obj) {
// 喚醒 使用 obj 調用 wait 方法的其中一個線程 (隨機)
obj.notify();
// 喚醒 使用呢 obj 調用 wait 方法的全部線程
obj.notifyAll();
}
}
}
複製代碼
博客地址:lantaoblog.site