synchronized關鍵字
- synchronized,這個東西我們一般稱之為同步鎖,是一個重量級鎖,在修飾代碼塊的時候需要傳入一個引用對象作為鎖的對象。
- Synchronized是非公平鎖。 Synchronized在線程進入ContentionList時,等待的線程會先嘗試自旋獲取鎖,如果獲取不到就進入ContentionList,這明顯對于已經進入隊列的線程是不公平的,還有一個不公平的事情就是自旋獲取鎖的線程還可能直接搶占OnDeck線程的鎖資源。
- 如何獲取鎖:當前已經獲取到鎖資源的線程(Owner)并不直接把鎖傳遞給正在競爭鎖資源的線程(OnDeck)線程,而是把鎖競爭的權利交給OnDeck,OnDeck需要重新競爭鎖。這樣雖然犧牲了一些公平性,但是能極大的提升系統的吞吐量,在JVM中,也把這種選擇行為稱之為“競爭切換”。
- 我們這里提到的鎖,是把所需要的代碼塊,資源,或數據鎖上,在操作他們的時候只允許一個線程去做操作。最終結果是為了保證cpu計算結果的正確性。
- synchronized可以把任意一個非NULL的對象當作鎖。
- 作用于方法時,鎖住的是對象的實例(this);
- 作用于靜態方法時,鎖住的是Class實例,又因為Class的相關數據存儲在永久帶PermGen(jdk1.8則是metaspace),永久帶是全局共享的,因此靜態方法鎖相當于類的一個全局鎖,會鎖所有調用該方法的線程;
- synchronized作用于一個對象實例時,鎖住的是所有以該對象為鎖的代碼塊。
類鎖,方法鎖,對象鎖
在修飾方法的時候默認是當前對象作為鎖的對象。
在修飾類的時候,默認是當前類的class對象作為鎖的對象。
注意:在修飾代碼塊時,需要一個reference對象作為鎖的對象。
所以存在類鎖,方法鎖,對象鎖這樣的概念。
類鎖,方法鎖,對象鎖
方法鎖
通過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。
synchronized 方法控制對類成員變量的訪問:
每個類實例對應一把鎖,每個 synchronized 方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨占該鎖,直到從該方法返回時才將鎖釋放,此后被阻塞的線程方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對于每一個類實例,其所有聲明為 synchronized 的成員函數中至多只有一個處于可執行狀態,從而有效避免了類成員變量的訪問沖突。
對象鎖
當一個對象中有同步方法或者同步塊,線程調用此對象進入該同步區域時,必須獲得對象鎖。如果此對象的對象鎖被其他調用者占用,則進入阻塞隊列,等待此鎖被釋放(同步塊正常返回或者拋異常終止,由JVM自動釋放對象鎖)。
注意,方法鎖也是一種對象鎖。當一個線程訪問一個帶synchronized方法時,由于對象鎖的存在,所有加synchronized的方法都不能被訪問(前提是在多個線程調用的是同一個對象實例中的方法)。
對象鎖有兩種形式:
第一種
public class object {
public synchronized void method{
System.out.println("我是對象鎖也是方法鎖");
}
}
第二種
public class object {
public void method{
synchronized(this){
System.out.println("我是對象鎖");
}
}
}
類鎖
一個class其中的靜態方法和靜態變量在內存中只會加載和初始化一份,所以,一旦一個靜態的方法被申明為synchronized,此類的所有的實例化對象在調用該方法時,共用同一把鎖,稱之為類鎖。
也是兩種形式:
第一種
public class object {
public static synchronized void method{
System.out.println("我是第一種類鎖");
}
}
第二種
public class object {
public void method{
synchronized (object.this) {
System.out.println("我是第二種類鎖");
}
}
}
重入鎖
重入鎖包括可重入鎖和不可重入鎖。
不可重入鎖:只判斷這個鎖有沒有被鎖上,只要被鎖上申請鎖的線程都會被要求等待。實現簡單。
當A方法獲取lock鎖去鎖住一段需要做原子性操作的B方法時,如果這段B方法又需要鎖去做原子性操作,那么A方法就必定要與B方法出現死鎖。這種會出現問題的重入一把鎖的情況,叫不可重入鎖。
- A方法需要等B方法執行完才能解鎖,但是B方法想執行完代碼又必須要lock鎖來加鎖。A的鎖未解鎖前,其他代碼塊無法使用此鎖來加鎖。這是由這個不可重入鎖決定的。
- 可重入鎖:不僅判斷鎖有沒有被鎖上,還會判斷鎖是誰鎖上的,當就是自己鎖上的時候,那么他依舊可以再次訪問臨界資源,并把加鎖次數加一。設計了加鎖次數,以在解鎖的時候,可以確保所有加鎖的過程都解鎖了,其他線程才能訪問。不然沒有加鎖的參考值,也就不知道什么時候解鎖?解鎖多少次?才能保證本線程已經訪問完臨界資源了可以喚醒其他線程訪問了。實現相對復雜。
- 總結:這個重入的概念就是,拿到鎖的代碼能不能多次以不同的方式訪問臨界資源而不出現死鎖等相關問題。經典之處在于判斷了需要使用鎖的線程是否為加鎖的線程。如果是,則擁有重入的能力。
Synchronized關鍵字底層原理屬于JVM層面。
修飾同步語句塊
synchronized 同步語句塊的實現,使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束位置。 當執行 monitorenter 指令時,線程試圖獲取鎖,也就是獲取 monitor ( monitor 對象存在于每個 Java 對象的對象頭中,synchronized 鎖便是通過這種方式獲取鎖的,這也是為什么 Java 中任意對象都可以作為鎖的原因) 的持有權。當計數器為0,則可以成功獲取,獲取后將鎖計數器設為1,也就是加1;相應的,在執行 monitorexit 指令后,將鎖計數器設為0,表明鎖被釋放。如果獲取對象鎖失敗,那當前線程就要阻塞等待,直到鎖被另外一個線程釋放為止。
修飾方法
synchronized 修飾方法,并沒有 monitorenter 指令和 monitorexit 指令,取得代之的是 ACC_SYNCHRonIZED 標識,該標識指明了該方法是一個同步方法,JVM 通過該 ACC_SYNCHRonIZED 訪問標志來辨別一個方法是否聲明為同步方法,從而執行相應的同步調用。


