synchronized的三種應用方式:java
實例方法
,當前實例加鎖,進入同步代碼前要得到當前實例的鎖;靜態方法
,當前類加鎖,進去同步代碼前要得到當前類對象的鎖;代碼塊
,這須要指定加鎖的對象,對所給的指定對象加鎖,進入同步代碼前要得到指定對象的鎖。用法總結以下:安全
注:併發
synchronized不能夠被繼承
,父類某個方法加了synchronized,若子類覆寫了該方法,子類要想同步還得在子類方法上加上synchronized關鍵字。【問題】實現一個容器,提供兩個方法,add,size。寫兩個線程,線程1添加10個元素到容器中,線程2實現監控元素的個數,當個數到5個時,線程2給出提示並結束。
public class MyContainer {app
private static List<Integer> lists = new ArrayList<>(); public static void main(String[] args) { final Object lock = new Object(); //監控線程 new Thread(()->{ synchronized (lock) { System.out.println("thread 2 start..."); if(lists.size() != 5) { try { lock.wait(); } catch (Exception e) { e.printStackTrace(); } } System.out.println("thread 2 end."); lock.notify(); } }, "t2").start(); new Thread(()->{ synchronized (lock) { for(int i = 0; i < 10; i++) { System.out.println("thread 1, add " + i); lists.add(i); if(lists.size() == 5) { lock.notify(); try { lock.wait(); } catch (Exception e) { e.printStackTrace(); } } try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } } }, "t1").start(); }
}性能
注:wait()會馬上釋放synchronized(obj)中的obj鎖,以便其餘線程能夠執行obj.notify(),可是notify()不會馬上馬上釋放sycronized(obj)中的obj鎖,必需要等notify()所在線程執行完synchronized(obj)塊中的全部代碼纔會釋放這把鎖
。因此,在t1中,調用了lock對象的notify方法以後,再調用lock的wait方法釋放鎖,而在t2被喚醒以後,繼續執行,最後還要調用lock對象的notify方法去喚醒此時處在wait狀態的t1優化
先經過下面簡單的例子看下:spa
public class Synchronize {操作系統
public static void main(String[] args) { synchronized (Synchronize.class){ System.out.println("Synchronize"); } }
}.net
使用 javap -c Synchronize
能夠查看編譯以後的具體信息線程
從編譯後的結果能夠看到:在同步方法調用前加了一個 monitorenter
指令,在退出方法和異常處插入了 monitorexit
的指令
實現原理:JVM 是經過進入、退出對象監視器( Monitor )
來實現對方法、同步塊的同步的,具體實現是在編譯以後同步代碼塊採用添加moniterenter、moniterexit
,同步方法使用ACC_SYNCHRONIZED
標記符隱式實現。每一個對象都有一個monitor與之關聯
,運行到moniterenter時嘗試獲取對應monitor的全部權,獲取成功就將monitor的進入數加1(因此是可重入鎖
,也被稱爲重量級鎖
),不然就阻塞,擁有monitor的線程運行到moniterexit時進入數減1,爲0時釋放monitor。其本質就是對一個對象監視器( Monitor )進行獲取,而這個獲取過程具備排他性從而達到了同一時刻只能一個線程訪問的目的
。Java內置的synchronized關鍵字能夠認爲是管程模型中的MESA模型
的簡化版。
Java對象與Monitor關聯關係示意圖以下:
JVM堆中存放的是對象實例,每個對象都有對象頭,對象頭裏有Mark Word,裏面存儲着對象的hashCode、GC分代年齡以及鎖信息。如圖所示,重量級鎖中存有指向monitor的指針。
其中ObjectMonitor中幾個關鍵字段的含義以下:
_count:記錄owner線程獲取鎖的次數。這句話很好理解,這也決定了synchronized是可重入的。
_owner:指向擁有該對象的線程
_WaitSet:主要存放全部wait的線程的對象,也就是說若是有線程處於wait狀態,將被掛入這個隊列,調用了wait()方法線程會進入該隊列
。
_EntryList:全部在等待獲取鎖的線程的對象,也就是說若是有線程處於等待獲取鎖的狀態的時候,將被掛入這個隊列。
詳情請參考:https://www.jianshu.com/p/32e...
詳細過程請參考:https://www.hollischuang.com/...
Java內存模型:
Java內存模型雖然有助加快執行速度,可是也帶來了新的問題。不一樣線程之間是沒法之間訪問對方工做內存中的變量,線程間的變量值傳遞均須要經過主內存來完成,那線程的操做結果怎麼讓其餘線程可見呢?這便須要先行發生
(happens-before
)原則來保證了。
先行發生規則中有以下2條:
即:若是有2個線程A和B,則根據規則1,A線程釋放鎖 happens-before B線程獲取鎖,根據規則2,那A線程的操做結果對B線程是可見的。
從上圖能夠看出,線程A會首先先從主內存中讀取共享變量a=0的值而後將該變量拷貝到本身的本地內存,進行加1操做後,再將該值刷新到主內存,整個過程即爲線程A 加鎖-->執行臨界區代碼-->釋放鎖相對應的內存語義。線程B獲取鎖的時候一樣會從主內存中讀取共享變量a的值,這個時候就是最新的值1,而後將該值拷貝到線程B的工做內存中去,釋放鎖的時候一樣會重寫到主內存中。
即:釋放鎖的時候會將值刷新到主內存中,其餘線程獲取鎖時會強制從主內存中獲取最新的值
。這也驗證了A happens-before B,A的執行結果對B是可見的。
詳情請參考:https://www.jianshu.com/p/151...
高效併發是從JDK 1.5 到 JDK 1.6的一個重要改進,HotSpot虛擬機開發團隊在這個版本中花費了很大的精力去對Java中的鎖進行優化,如適應性自旋、鎖消除、鎖粗化、輕量級鎖和偏向鎖
等。這些技術都是爲了在線程之間更高效的共享數據,以及解決競爭問題。但對Java開發者而言,只須要知道想在加鎖的時候使用synchronized就能夠了,具體的鎖的優化是虛擬機根據競爭狀況自行決定的
。
因爲Java的線程是映射到操做系統原生線程之上的,若是要阻塞或喚醒一個線程就須要操做系統的幫忙,這就要從用戶態轉換到內核態,所以狀態轉換須要花費不少的處理器時間,因此優化的想法主要是能不阻塞線程就不阻塞。
線程不停地執行循環體
,不進行線程狀態的改變。若是在鎖被佔用的時間很短的狀況下,自旋等待的效果會很是好,反之,若是鎖被佔用的時間很長,自旋就會浪費CPU,因此自旋要有必定限度。在JDK1.6後,自旋的時間再也不固定
,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定,這即是自適應自旋了。逃逸分析
,判斷出代碼塊中不存在多個線程共享的數據,便會在編譯後將鎖去掉
。好比:咱們常常在代碼中使用StringBuffer做爲局部變量,而StringBuffer中的append是線程安全的,有synchronized修飾的,可是做爲局部變量並不須要共享,因此這個時候便會進行鎖消除的優化。加鎖的範圍擴大
。
會被粗化成:
經過CAS操做嘗試把monitor的_owner字段設置爲當前線程
,若是更新成功了,那麼代表這個線程就擁有了該對象的鎖,並將對象頭的Mark Word的鎖標誌位轉變爲"00",即表明此對象處於輕量級鎖定狀態。若是更新失敗,則膨脹爲重量級鎖,等待鎖的線程須要進入阻塞狀態。經過ObjectMonitor類的源碼能夠看出:鎖會偏向於第一個得到它的線程,若是在接下來執行過程當中,該鎖沒有被其餘線程獲取,則持有偏向鎖的線程將永遠不須要再進行同步
。若是說輕量級鎖是在無競爭的狀況下使用CAS操做去消除同步使用的互斥量,那麼偏向鎖就是在無競爭的狀況下把整個同步都消除掉,連CAS操做都不作了。但這一切都是在無競爭的狀況下,若是有另一個線程嘗試去獲取這個鎖,那偏向模式便宣告結束。細節請參考:https://www.hollischuang.com/...
本文從synchronized的用法開始,而後逐步深刻介紹synchronized的實現原理,其實質是對管程模型的一種實現。雖然在用的時候就是一個關鍵字,但背後的內容卻十分豐富,寫本文的過程當中,參考了許多大牛的博客,受益良多。
https://blog.csdn.net/weixin_...
https://www.jianshu.com/p/32e...
https://www.jianshu.com/p/d53...
https://www.hollischuang.com/...
https://www.hollischuang.com/...
https://www.hollischuang.com/...
https://www.jianshu.com/p/e62...
https://www.jianshu.com/p/27f...
https://www.jianshu.com/p/151...