一、為什么要使用 synchronized
使用synchronized
的原因在于:它能夠確保多個(gè)線程在同一時(shí)刻躏嚎,只能有一個(gè)線程處于方法或者同步塊中,它保證了線程對(duì)變量訪問(wèn)的可見(jiàn)性和排他性。
二堡牡、synchronized 原理
在JDK 1.6
之前则涯,synchronized
的實(shí)現(xiàn)是基于對(duì)象上的監(jiān)視器复局,這也被稱(chēng)為重量鎖。默認(rèn)情況下粟判,每一個(gè)對(duì)象都有一個(gè)關(guān)聯(lián)的Monitor
亿昏,而每個(gè)Monitor
包含了一個(gè)EntryCount
計(jì)數(shù)器,它是synchronized
實(shí)現(xiàn)可重入的關(guān)鍵档礁。
在JDK 1.6
之后角钩,對(duì)鎖進(jìn)行了一系列優(yōu)化的措施,通過(guò)引入自旋鎖呻澜、適應(yīng)性自旋鎖递礼、鎖消除、鎖粗化羹幸、偏向鎖脊髓、輕量級(jí)鎖等技術(shù)來(lái)減少鎖操作的開(kāi)銷(xiāo)。
這些優(yōu)化措施最終的目的是減少鎖操作的開(kāi)銷(xiāo)栅受,然而它所改變的只是鎖的實(shí)現(xiàn)方式将硝,但是加鎖和解鎖這一基本原則是沒(méi)有改變的恭朗。這篇文章主要是介紹synchronized
的使用,因此依疼,在后面的介紹中痰腮,我們還是按照比較容易理解的重量鎖的方式進(jìn)行分析,在之后的文章中律罢,我們?cè)賮?lái)談一下優(yōu)化后的實(shí)現(xiàn)策略膀值。
2.1 進(jìn)入同步方法或者代碼塊
當(dāng)一個(gè)線程執(zhí)行某個(gè)對(duì)象的同步方法或者代碼塊時(shí),會(huì)先檢查這個(gè)對(duì)象所關(guān)聯(lián)的Monitor's EntryCount
是否為0
:
- 如果
EntryCount
為0
弟翘,那么該線程就會(huì)將Monitor’s EntryCount
設(shè)置為1
虫腋,并成為該Monitor
的所有者,接著執(zhí)行該方法或者代碼塊中的語(yǔ)句稀余。 - 如果
EntryCount
不為0
悦冀,這時(shí)會(huì)去檢查對(duì)象所關(guān)聯(lián)的Monitor
的持有者是哪一個(gè)線程: - 第一種情況:持有該
Monitor
的線程就是當(dāng)前正在嘗試獲取Monitor
的線程,那么將EntryCount
的數(shù)值加1
睛琳,繼續(xù)執(zhí)行方法或者代碼塊中的語(yǔ)句盒蟆。 - 第二種情況:持有該
Monitor
的是其它的線程,那么該線程進(jìn)入阻塞狀態(tài)师骗,直到EntryCount
的數(shù)值變?yōu)?code>0历等。
2.2 退出同步方法或者代碼塊
當(dāng)一個(gè)線程從同步方法或者代碼塊退出時(shí),會(huì)將EntryCount
減1
辟癌,如果EntryCount
變?yōu)?code>0寒屯,那么該線程會(huì)釋放它所持有的Monitor
。之前那些阻塞在synchronized
的線程會(huì)嘗試去獲取Monitor
黍少,成功獲取Monitor
的線程可以進(jìn)入同步方法或者代碼塊寡夹。
三、synchronized 使用
對(duì)于synchronized
的使用厂置,我們有兩種分類(lèi)方法:
- 根據(jù)使用場(chǎng)景分類(lèi)
- 根據(jù)
Monitor
關(guān)聯(lián)的對(duì)象分類(lèi)菩掏。
3.1 根據(jù)使用場(chǎng)景分類(lèi)
很多介紹synchronized
的文章,都是通過(guò)使用場(chǎng)景進(jìn)行分類(lèi)的昵济,一般來(lái)說(shuō)可以分為如下四種使用場(chǎng)景智绸,而每種場(chǎng)景下根據(jù)Monitor
所關(guān)聯(lián)的對(duì)象不同,又會(huì)衍生出另外的用法:
- 靜態(tài)方法
//靜態(tài)方法访忿,使用的是Class類(lèi)鎖
synchronized public static void staticMethod() {}
- 靜態(tài)方法代碼塊
private static final byte[] mStaticLockByte = new byte[1];
//靜態(tài)方法代碼塊1瞧栗,使用的是Class類(lèi)鎖
public static void staticBlock1() {
synchronized (SynchronizedObject.class) {}
}
//靜態(tài)方法代碼塊2,使用的是內(nèi)部靜態(tài)變量鎖
public static void staticBlock2() {
synchronized (mStaticLockByte) {}
}
- 普通方法
//普通方法醉顽,使用的是調(diào)用該方法的對(duì)象鎖
synchronized public void method() {}
- 普通方法代碼塊
private static final byte[] mStaticLockByte = new byte[1];
private final byte[] mLockByte = new byte[1];
//普通方法代碼塊1沼溜,使用的是Class類(lèi)鎖
public void block1() {
synchronized (SynchronizedObject.class) {}
}
//普通方法代碼塊2,使用的是mLockByte的變量鎖
public void block2() {
synchronized (mLockByte) {} //變量需要聲明為final
}
//普通方法代碼塊3游添,使用的是mStaticLockByte的變量鎖
public void block3() {
synchronized (mStaticLockByte) {}
}
//普通方法代碼塊4系草,使用的是調(diào)用該方法的對(duì)象鎖
public void block4() {
synchronized (this) {}
}
3.2 根據(jù) Monitor 關(guān)聯(lián)的對(duì)象分類(lèi)
根據(jù)使用場(chǎng)景進(jìn)行分類(lèi)通熄,主要是為了讓大家知道如何使用synchronized
關(guān)鍵字,然而要真正地理解synchronized
找都,就需要結(jié)合第二節(jié)談到的synchronized
原理唇辨,其實(shí)3.1
中談到的多種場(chǎng)景,都是和Monitor
有關(guān)能耻,那么從和Monitor
關(guān)聯(lián)的對(duì)象來(lái)看赏枚,我們重新對(duì)3.1
中的8
種場(chǎng)景重新進(jìn)行分類(lèi):
-
Class
對(duì)象: - 靜態(tài)方法
- 靜態(tài)方法代碼塊1 -
SynchronizedObject.class
- 普通方法代碼塊1 -
SynchronizedObject.class
- 調(diào)用方法的對(duì)象
- 普通方法
- 普通方法代碼塊4 -
this
- 靜態(tài)對(duì)象
- 靜態(tài)方法代碼塊2 -
mStaticLockByte
- 普通方法代碼塊3 -
mStaticLockByte
- 非靜態(tài)對(duì)象
- 普通方法代碼塊1 -
mLockByte
如果使用場(chǎng)景屬于上面的同一個(gè)分類(lèi)當(dāng)中,那么才有可能產(chǎn)生線程阻塞在synchronized
關(guān)鍵字的情況晓猛,舉一個(gè)例子饿幅,如果A
線程通過(guò)靜態(tài)方法訪問(wèn)(分類(lèi)一)并且沒(méi)有從該方法退出:
- 這時(shí)
B
線程是通過(guò)一個(gè)對(duì)象的普通方法來(lái)訪問(wèn)(分類(lèi)二),那么是不會(huì)阻塞的戒职,這是因?yàn)檎{(diào)用該方法的對(duì)象所關(guān)聯(lián)的Monitor
沒(méi)有被持有栗恩。 - 如果
B
線程使用的是靜態(tài)方法代碼塊來(lái)訪問(wèn),而該靜態(tài)方法代碼塊使用的是SynchronizedObject.class
來(lái)修飾(分類(lèi)一)洪燥,由于這兩種使用場(chǎng)景是屬于同一個(gè)分類(lèi)磕秤,那么就會(huì)B
線程就會(huì)進(jìn)入阻塞狀態(tài),這是因?yàn)?code>SynchronizedObject類(lèi)所關(guān)聯(lián)的Monitor
已經(jīng)被A
線程持有了捧韵。
四市咆、小結(jié)
從表面上來(lái)看,synchronized
的使用可以簡(jiǎn)單地分為同步方法和同步代碼塊再来,但是究竟在什么情況下會(huì)導(dǎo)致一個(gè)線程在synchronized
上阻塞蒙兰,則需要分析synchronized
方法所嘗試獲取的Monitor
的是否已經(jīng)被其它線程持有了。