1.Synchronized的實現原理
同步代碼塊使用monitorenter
和monitorexit
兩個指令實現,可以把執行monitorenter
指令理解爲加鎖,執行monitorexit
理解爲釋放鎖。 每個對象維護着一個記錄着被鎖次數的計數器。未被鎖定的對象的該計數器爲0,當一個線程獲得鎖(執monitorenter
)後,該計數器自增變爲 1 ,當同一個線程再次獲得該對象的鎖的時候,計數器再次自增。當同一個線程釋放鎖(執行monitorexit
指令)的時候,計數器再自減。當計數器爲0的時候。鎖將被釋放,其他線程便可以獲得鎖。
每個對象自身維護這一個被加鎖次數的計數器,當計數器數字爲0時表示可以被任意線程獲得鎖。當計數器不爲0時,只有獲得鎖的線程才能再次獲得鎖。即可重入鎖。
原子性的保證:通過monitorenter
和monitorexit
指令,可以保證被synchronized
修飾的代碼在同一時間只能被一個線程訪問,在鎖未釋放之前,無法被其他線程訪問到。
可見性的保證:synchornized保證在對一個變量解鎖之前,將這個變量刷新到主存。
Synchronized是無法保證有序性的
在JIT編譯過程中,虛擬機會根據情況使用這三種技術對鎖進行優化,目的是減少鎖的競爭,提升性能。
鎖的優化
1.自旋鎖:
2.鎖消除:
//因爲以下代碼中hollis是一個局部變量,不會有其他線程來訪問的
//所以是沒必要加鎖的,所以在JIT編譯階段就會被優化掉
public void f() {
Object object = new Object();
synchronized(object) {
System.out.println(object);
}
}
//優化之後的代碼是
public void f() {
Object object= new Object();
System.out.println(object);
}
3.鎖粗化:
一般情況下,我們將的都是鎖細化,就是不要把多餘的操作放到同步代碼中,鎖的內部只寫與併發有關的內容,這樣有助於提高效率。大部分情況是如此。但是有種情況比較特殊:如果一連續操作都是對同一對象加鎖解鎖,甚至加鎖操作出現在循環體中,JIT會將加鎖的範圍拓展到外圍, 這其實和我們要求的減小鎖粒度並不衝突。減小鎖粒度強調的是不要在銀行櫃檯前做準備工作以及和辦理業務無關的事情。而鎖粗化建議的是,同一個人,要辦理多個業務的時候,可以在同一個窗口一次性辦完,而不是多次取號多次辦理。
for(int i=0;i<100000;i++){
synchronized(this){
do();
}
//鎖粗化之後
synchronized(this){
for(int i=0;i<100000;i++){
do();
}