先來(lái)簡(jiǎn)單理解一下對(duì)象鎖和類鎖:
Java對(duì)象鎖
對(duì)象鎖是用于對(duì)象實(shí)例方法硅则,或者一個(gè)對(duì)象實(shí)例上的
有一個(gè)類A,A里面有一些方法或者代碼塊使用了對(duì)象鎖。如:
class A{
//同步方法
public void test1() {
synchronized (this) {
}
}
//跟上面的test1方法一樣都是對(duì)象鎖,只是鎖的尺寸不同
public synchronized void test2() {
}
//方法二
public void test3() {
}
}
現(xiàn)在有線程1new了一個(gè)A的對(duì)象昼汗,對(duì)象為a,并調(diào)用了使用對(duì)象鎖方法test1()旺坠,即:
A a = new A();
a.test1();
---到此是線程1執(zhí)行的代碼---
此時(shí)當(dāng)線程1在執(zhí)行加了對(duì)象鎖的同步方法test1的那一刻乔遮,這時(shí)又有一個(gè)線程2過(guò)來(lái)扮超,想要執(zhí)行下面這段代碼會(huì)怎么樣呢取刃?
a.test2();
---到此是線程2執(zhí)行的代碼---
如果是正常的多線程場(chǎng)景蹋肮,同一時(shí)刻,線程1執(zhí)行a.test1()和線程2執(zhí)行a.test2()是互不影響的璧疗。
但是這里的線程2必須等線程1釋放對(duì)象鎖之后才可以執(zhí)行test2()坯辩,因?yàn)椋?/strong>
test1()方法加了synchronized對(duì)象鎖,那在線程1執(zhí)行對(duì)象a的test1()方法時(shí)崩侠,就會(huì)鎖住對(duì)象實(shí)例a的內(nèi)存漆魔,在線程1還沒(méi)釋放對(duì)象鎖之前,任何線程都是沒(méi)辦法進(jìn)入對(duì)象a里面的却音。而線程1只會(huì)在執(zhí)行完同步方法或者同步代碼塊只會(huì)才會(huì)釋放對(duì)象鎖改抡。
但,如果線程2做的是這么一個(gè)操作:
a.test3();
這個(gè)時(shí)候線程3執(zhí)行test2方法就不需要等待線程1系瓢,進(jìn)行了同步的方法(加鎖方法)和沒(méi)有進(jìn)行同步的方法(普通方法)是互不影響的阿纤,一個(gè)線程進(jìn)入了同步方法,得到了對(duì)象鎖夷陋,其他線程還是可以訪問(wèn)那些沒(méi)有同步的方法(普通方法)欠拾。這里涉及到內(nèi)置鎖的一個(gè)概念(此概念出自java并發(fā)編程實(shí)戰(zhàn)第二章):
對(duì)象的內(nèi)置鎖和對(duì)象的狀態(tài)之間是沒(méi)有內(nèi)在的關(guān)聯(lián)的,雖然大多數(shù)類都將內(nèi)置鎖用做一種有效的加鎖機(jī)制骗绕,但對(duì)象的域并不一定通過(guò)內(nèi)置鎖來(lái)保護(hù)藐窄。當(dāng)獲取到與對(duì)象關(guān)聯(lián)的內(nèi)置鎖時(shí),并不能阻止其他線程訪問(wèn)該對(duì)象酬土,當(dāng)某個(gè)線程獲得對(duì)象的鎖之后荆忍,只能阻止其他線程獲得同一個(gè)鎖。之所以每個(gè)對(duì)象都有一個(gè)內(nèi)置鎖撤缴,是為了免去顯式地創(chuàng)建鎖對(duì)象东揣。
在微服務(wù)的后端中,每個(gè)不同的請(qǐng)求都會(huì)新建一個(gè)對(duì)象來(lái)handle腹泌,理論上對(duì)象鎖是不會(huì)造成我們想要的約束的
Java類鎖
類鎖是用于類的靜態(tài)方法或者一個(gè)類的class對(duì)象上的
同樣的例子嘶卧,有一個(gè)類A,只是A里面使用synchronize的方法不一樣了凉袱,這種使用方法會(huì)鎖住類A芥吟。如:
class A{
//同步方法
public void test1() {
synchronized (A.class) {
}
}
//跟上面的同步方法一樣,都是類鎖专甩,只是表現(xiàn)形式不同
public static synchronized void test2() {
}
public synchronized void test3() {
}
}
現(xiàn)在有線程1new了一個(gè)A的對(duì)象钟鸵,對(duì)象為a,并調(diào)用了使用對(duì)象鎖方法test1()涤躲,即:
A a = new A();
a.test1();
---到此是線程1執(zhí)行的代碼---
此時(shí)當(dāng)線程1在執(zhí)行加了對(duì)象鎖的同步方法test1的那一刻棺耍,這時(shí)又有一個(gè)線程2過(guò)來(lái),想要執(zhí)行下面這段代碼會(huì)怎么樣呢种樱?
A.test2();
---到此是線程2執(zhí)行的代碼---
這里的線程2必須等線程1釋放類鎖之后才可以執(zhí)行A.test2()蒙袍,因?yàn)椋?/strong>
線程1執(zhí)行的test1()方法里面鎖住了A.class俊卤,也就是鎖住了類A的內(nèi)存。那同一時(shí)刻所有實(shí)例想使用test1方法或者是其他加了類鎖的形如A.test2()方法害幅,都得等線程1釋放類鎖后才能執(zhí)行消恍。線程2要用加了類鎖的靜態(tài)方法test2,那自然是用不了了以现。
想一下狠怨,如果是在高并發(fā)的情況下,所有要執(zhí)行類A的test1請(qǐng)求都得阻塞等待最先進(jìn)入類A的類鎖的線程執(zhí)行完邑遏,釋放類鎖后佣赖,才能執(zhí)行。如果這個(gè)鎖住的方法執(zhí)行的時(shí)候過(guò)長(zhǎng)记盒,或者直接死循環(huán)茵汰,那整個(gè)系統(tǒng)將會(huì)被拖垮甚至癱瘓。
但孽鸡,如果線程2做的是這么一個(gè)操作:
a.test3();
結(jié)果是線程1執(zhí)行了類鎖的同步方法蹂午,線程2執(zhí)行了對(duì)象鎖的同步方法,兩者互不影響彬碱。這證明了類鎖和對(duì)象鎖是兩個(gè)不一樣的鎖豆胸,控制著不同的區(qū)域,它們是互不干擾的巷疼。同樣晚胡,線程獲得對(duì)象鎖的同時(shí),也可以獲得該類鎖嚼沿,即同時(shí)獲得兩個(gè)鎖估盘,這是允許的。
在微服務(wù)的后端中骡尽,我們常用類鎖來(lái)進(jìn)行約束
其實(shí)總結(jié)起來(lái)很簡(jiǎn)單:
- 一個(gè)鎖的是類對(duì)象遣妥,一個(gè)鎖的是實(shí)例對(duì)象。
- 若類對(duì)象被lock攀细,則類對(duì)象的所有同步方法全被lock箫踩;
- 若實(shí)例對(duì)象被lock,則該實(shí)例對(duì)象的所有同步方法全被lock
到這里大家應(yīng)該對(duì)對(duì)象鎖和類鎖應(yīng)該有個(gè)更加清晰的理解了
注意:
上面我們有講到synchronize的一個(gè)缺陷谭贪,就是如果同步的方法死循環(huán)了或者執(zhí)行時(shí)間過(guò)長(zhǎng)了境钟,會(huì)影響系統(tǒng)的正常運(yùn)行。那么為了將這種風(fēng)險(xiǎn)降到最低俭识,一般我們使用類鎖的時(shí)候慨削,不會(huì)直接synchronize(xx.class)或者static synchronized去鎖住這個(gè)類的所有同步方法,而是用先在這個(gè)類里面聲明了一個(gè)對(duì)象實(shí)例:Object object=new Object(),然后再synchronize(object)缚态。那么這個(gè)方法加鎖的對(duì)象是object這個(gè)對(duì)象磁椒,當(dāng)一個(gè)線程執(zhí)行這個(gè)方法時(shí),這對(duì)其他線程要執(zhí)行這個(gè)類的其他同步方法是沒(méi)有影響的猿规,只會(huì)影響到要執(zhí)行用synchronize(object)鎖住的方法,因?yàn)樗麄兂钟械逆i都完全不一樣宙橱。
總結(jié)五種用法:
一姨俩、this
synchronized(this){
//互斥代碼
}
這里的this指的是執(zhí)行這段代碼的對(duì)象,synchronized得到的鎖就是this這個(gè)對(duì)象的鎖
public synchronized void func(){
//互斥代碼
}
二师郑、A.class
synchronized(A.class){
//互斥代碼
}
這里A.class得到的是A這類环葵,所以synchronized關(guān)鍵字得到的鎖是類的鎖,這種方法同下面的方法功能是相同的:
public static synchronized void fun(){
//互斥代碼
}
所有需要類的鎖的方法都不能同時(shí)執(zhí)行宝冕,但是它和需要某個(gè)對(duì)象的鎖的方法或者是不需要任何鎖的方法可以同時(shí)執(zhí)行张遭。
三、object.getClass()
synchronized(object.getClass){
//互斥代碼
}
這種方法一般情況下同第二種是相同地梨,但是出現(xiàn)繼承和多態(tài)時(shí)菊卷,得到的結(jié)果卻是不相同的。所以一般情況下推薦使用A.class的方式宝剖。
四洁闰、object
private Object lock = new Object();
public void test1(){
synchronized(lock){
//互斥代碼
}
}
這里synchronized關(guān)鍵字拿到的鎖是對(duì)象object的鎖,所有需要這個(gè)對(duì)象的鎖的方法都不能同時(shí)執(zhí)行万细。這是最常用的高并發(fā)場(chǎng)景下要鎖住某個(gè)方法所用的操作扑眉。
五、static object
上邊的代碼稍作修改就可以起到互斥作用赖钞,將類中Object對(duì)象的聲明改為下面這樣:
private static Object lock = new Object();
這樣不同的類使用的就是同一個(gè)object對(duì)象腰素,需要的鎖也是同一個(gè)鎖,就可以達(dá)到互斥的效果了雪营。