1、synchronized的基本使用
Synchronized的作用主要有三個(gè):(1)確保線程互斥的訪問同步代碼(2)保證共享變量的修改能夠及時(shí)可見(3)有效解決重排序問題。
從語(yǔ)法上講,Synchronized總共有三種用法:
(1)修飾普通方法
(2)修飾靜態(tài)方法(對(duì)class的對(duì)象鎖)
(3)修飾代碼塊
public class SynchronizedTest {
public synchronized void method1(){
System.out.println("Method 1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public static synchronized void method2(){
System.out.println("Method 2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public void method3(){
System.out.println("Method 3 start");
try {
synchronized (this) {
System.out.println("Method 3 execute");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 3 end");
}
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
SynchronizedTest.method2();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method3();
}
}).start();
}
}
2、深入synchronized

Synchronized是通過對(duì)象內(nèi)部的一個(gè)叫做監(jiān)視器鎖(monitor)來(lái)實(shí)現(xiàn)的。但是監(jiān)視器鎖本質(zhì)又是依賴于底層的操作系統(tǒng)的Mutex Lock來(lái)實(shí)現(xiàn)的。而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換這就需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)成本非常高,狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間,這就是為什么Synchronized效率低的原因。因此,這種依賴于操作系統(tǒng)Mutex Lock所實(shí)現(xiàn)的鎖我們稱之為“重量級(jí)鎖”。JDK中對(duì)Synchronized做的種種優(yōu)化,其核心都是為了減少這種重量級(jí)鎖的使用。JDK1.6以后,為了減少獲得鎖和釋放鎖所帶來(lái)的性能消耗,提高性能,引入了“偏向鎖”和“輕量級(jí)鎖”。
java對(duì)象頭與對(duì)象鎖

偏向鎖
偏向鎖,顧名思義,它會(huì)偏向于第一個(gè)訪問鎖的線程,如果在接下來(lái)的運(yùn)行過程中,該鎖沒有被其他的線程訪問,則持有偏向鎖的線程將永遠(yuǎn)不需要觸發(fā)同步。 如果在運(yùn)行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會(huì)被掛起,JVM會(huì)嘗試消除它身上的偏向鎖,將鎖恢復(fù)到標(biāo)準(zhǔn)的輕量級(jí)鎖。(偏向鎖只能在單線程下起作用),其流程如圖所示:

偏向鎖獲取過程:
(1)訪問Mark Word中偏向鎖的標(biāo)識(shí)是否設(shè)置成1,鎖標(biāo)志位是否為01——確認(rèn)為可偏向狀態(tài)。
?。?)如果為可偏向狀態(tài),則測(cè)試線程ID是否指向當(dāng)前線程,如果是,進(jìn)入步驟(5),否則進(jìn)入步驟(3)。
?。?)如果線程ID并未指向當(dāng)前線程,則通過CAS操作競(jìng)爭(zhēng)鎖。如果競(jìng)爭(zhēng)成功,則將Mark Word中線程ID設(shè)置為當(dāng)前線程ID,然后執(zhí)行(5);如果競(jìng)爭(zhēng)失敗,執(zhí)行(4)。
?。?)如果CAS獲取偏向鎖失敗,則表示有競(jìng)爭(zhēng)。當(dāng)?shù)竭_(dá)全局安全點(diǎn)(safepoint)時(shí)獲得偏向鎖的線程被掛起,偏向鎖升級(jí)為輕量級(jí)鎖,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼。
?。?)執(zhí)行同步代碼。
偏向鎖的釋放:
偏向鎖的撤銷在上述第四步驟中有提到。偏向鎖只有遇到其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖,線程不會(huì)主動(dòng)去釋放偏向鎖(不主動(dòng)釋放)。偏向鎖的撤銷,需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒有字節(jié)碼正在執(zhí)行),它會(huì)首先暫停擁有偏向鎖的線程,判斷鎖對(duì)象是否處于被鎖定狀態(tài),撤銷偏向鎖后恢復(fù)到未鎖定(標(biāo)志位為“01”)或輕量級(jí)鎖(標(biāo)志位為“00”)的狀態(tài)。
輕量級(jí)鎖
輕量級(jí)鎖(Lightweight Locking)本意是在沒有多線程競(jìng)爭(zhēng)的前提下(即多線程交替執(zhí)行互斥代碼情況下),減少傳統(tǒng)的重量級(jí)鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗,是為了減少多線程進(jìn)入互斥的幾率,并不是要替代互斥。 它利用了CPU原語(yǔ)Compare-And-Swap(CAS,匯編指令CMPXCHG),嘗試在進(jìn)入互斥前,進(jìn)行補(bǔ)救。通過上表我們可以知道00標(biāo)記為輕量級(jí)鎖,其流程:

輕量級(jí)鎖獲取過程
(1)在代碼進(jìn)入同步塊的時(shí)候,如果同步對(duì)象鎖狀態(tài)為無(wú)鎖狀態(tài)(鎖標(biāo)志位為“01”狀態(tài),是否為偏向鎖為“0”),虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的Mark Word的拷貝,官方稱之為 Displaced Mark Word。這時(shí)候線程堆棧與對(duì)象頭的狀態(tài)如圖2.1所示。
(2)拷貝對(duì)象頭中的Mark Word復(fù)制到鎖記錄中。
(3)拷貝成功后,虛擬機(jī)將使用CAS操作嘗試將對(duì)象的Mark Word更新為指向Lock Record的指針,并將Lock record里的owner指針指向object mark word。如果更新成功,則執(zhí)行步驟(4),否則執(zhí)行步驟(5)。
(4)如果這個(gè)更新動(dòng)作成功了,那么這個(gè)線程就擁有了該對(duì)象的鎖,并且對(duì)象Mark Word的鎖標(biāo)志位設(shè)置為“00”,即表示此對(duì)象處于輕量級(jí)鎖定狀態(tài),這時(shí)候線程堆棧與對(duì)象頭的狀態(tài)如圖2.2所示。
(5)如果這個(gè)更新操作失敗了,虛擬機(jī)首先會(huì)檢查對(duì)象的Mark Word是否指向當(dāng)前線程的棧幀,如果是就說明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖,那就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行。否則說明多個(gè)線程競(jìng)爭(zhēng)鎖,輕量級(jí)鎖就要膨脹為重量級(jí)鎖,鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”,Mark Word中存儲(chǔ)的就是指向重量級(jí)鎖(互斥量)的指針,后面等待鎖的線程也要進(jìn)入阻塞狀態(tài)。 而當(dāng)前線程便嘗試使用自旋來(lái)獲取鎖,自旋就是為了不讓線程阻塞,而采用循環(huán)去獲取鎖的過程。


輕量級(jí)鎖釋放過程
(1)通過CAS操作嘗試把線程中復(fù)制的Displaced Mark Word對(duì)象替換當(dāng)前的Mark Word。
(2)如果替換成功,整個(gè)同步過程就完成了。
(3)如果替換失敗,說明有其他線程嘗試過獲取該鎖(此時(shí)鎖已膨脹),那就要在釋放鎖的同時(shí),喚醒被掛起的線程。
總結(jié):


其他優(yōu)化
(1)、適應(yīng)性自旋(Adaptive Spinning):
從輕量級(jí)鎖獲取的流程中我們知道,當(dāng)線程在獲取輕量級(jí)鎖的過程中執(zhí)行CAS操作失敗時(shí),是要通過自旋來(lái)獲取重量級(jí)鎖的。問題在于,自旋是需要消耗CPU的,如果一直獲取不到鎖的話,那該線程就一直處在自旋狀態(tài),白白浪費(fèi)CPU資源。解決這個(gè)問題最簡(jiǎn)單的辦法就是指定自旋的次數(shù),例如讓其循環(huán)10次,如果還沒獲取到鎖就進(jìn)入阻塞狀態(tài)。但是JDK采用了更聰明的方式——適應(yīng)性自旋,簡(jiǎn)單來(lái)說就是線程如果自旋成功了,則下次自旋的次數(shù)會(huì)更多,如果自旋失敗了,則自旋的次數(shù)就會(huì)減少。
(2)、鎖粗化
鎖粗化的概念應(yīng)該比較好理解,就是將多次連接在一起的加鎖、解鎖操作合并為一次,將多個(gè)連續(xù)的鎖擴(kuò)展成一個(gè)范圍更大的鎖。舉個(gè)例子:
public class StringBufferTest {
StringBuffer stringBuffer = new StringBuffer();
public void append(){
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
}
}
這里每次調(diào)用stringBuffer.append方法都需要加鎖和解鎖,如果虛擬機(jī)檢測(cè)到有一系列連串的對(duì)同一個(gè)對(duì)象加鎖和解鎖操作,就會(huì)將其合并成一次范圍更大的加鎖和解鎖操作,即在第一次append方法時(shí)進(jìn)行加鎖,最后一次append方法結(jié)束后進(jìn)行解鎖。
(3)、鎖消除
鎖消除即刪除不必要的加鎖操作。根據(jù)代碼逃逸技術(shù),如果判斷到一段代碼中,堆上的數(shù)據(jù)不會(huì)逃逸出當(dāng)前線程,那么可以認(rèn)為這段代碼是線程安全的,不必要加鎖??聪旅孢@段程序:
public class SynchronizedTest02 {
public static void main(String[] args) {
SynchronizedTest02 test02 = new SynchronizedTest02();
//啟動(dòng)預(yù)熱
for (int i = 0; i < 10000; i++) {
i++;
}
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
test02.append("abc", "def");
}
System.out.println("Time=" + (System.currentTimeMillis() - start));
}
public void append(String str1, String str2) {
StringBuffer sb = new StringBuffer();
sb.append(str1).append(str2);
}
}
雖然StringBuffer的append是一個(gè)同步方法,但是這段程序中的StringBuffer屬于一個(gè)局部變量,并且不會(huì)從該方法中逃逸出去,所以其實(shí)這過程是線程安全的,可以將鎖消除。下面是本地執(zhí)行的結(jié)果:

注:可能JDK各個(gè)版本之間執(zhí)行的結(jié)果不盡相同,我這里采用的JDK版本為1.6
3、synchronized源碼解析
參考小狼的:http://www.itdecent.cn/p/c5058b6fe8e5
4、深入分析Object.wait/notify實(shí)現(xiàn)機(jī)制

Object.wait/notify實(shí)現(xiàn)機(jī)制在HotSpot虛擬機(jī)中,monitor采用ObjectMonitor實(shí)現(xiàn)。ObjectMonitor對(duì)象中有兩個(gè)隊(duì)列:_WaitSet 和 _EntryList,用來(lái)保存ObjectWaiter對(duì)象列表;_owner指向獲得ObjectMonitor對(duì)象的線程。
處于等待鎖block狀態(tài)的線程,會(huì)被加入到entry set;處于wait狀態(tài)的線程,會(huì)被加入到wait set;notify方法會(huì)獲取_WaitSet列表中的第一個(gè)ObjectWaiter節(jié)點(diǎn),根據(jù)不同的策略,將取出來(lái)的ObjectWaiter節(jié)點(diǎn),加入到_EntryList或則通過Atomic::cmpxchg_ptr指令進(jìn)行自旋操作cxq。
參考:
http://www.itdecent.cn/p/f4454164c017
https://www.cnblogs.com/wewill/p/8058292.html