synchronized 在我們平常工作中也是挺常用的, 對(duì)于擺脫多線程問題很有幫助。但是如果synchronized被錯(cuò)誤使用時(shí),可能會(huì)給我們帶來很多麻煩。
在本文中,我們將討論與同步相關(guān)的一些不好的做法,以及針對(duì)每個(gè)使用情況的更好的方法。
同步的原則
一般來說,我們應(yīng)該只對(duì)那些我們確信沒有外部代碼會(huì)鎖定的對(duì)象進(jìn)行同步。
換句話說,使用池化或可重復(fù)使用的對(duì)象進(jìn)行同步是一種不好的做法。原因是池化/可重用對(duì)象可以被JVM中的其他進(jìn)程訪問,外部/不被信任的代碼對(duì)這些對(duì)象的任何修改都會(huì)導(dǎo)致死鎖和非確定性行為。
現(xiàn)在,讓我們來討論基于某些類型的同步原則,如String、Boolean、Integer和Object。
String 字面量
1.錯(cuò)誤用法
字符串字面量是有池的,在Java中經(jīng)常被重復(fù)使用。因此,不建議使用String類型與 synchronized關(guān)鍵字進(jìn)行同步。
public void stringBadPractice1() {
String stringLock = "LOCK_STRING";
synchronized (stringLock) {
// ...
}
}
同樣地,如果我們使用private final String字面,它仍然是從常量池中引用的。
private final String stringLock = "LOCK_STRING";
public void stringBadPractice2() {
synchronized (stringLock) {
// ...
}
}
此外,為了同步,內(nèi)接字符串被認(rèn)為是不好的做法。
private final String internedStringLock = new String("LOCK_STRING").intern();
public void stringBadPractice3() {
synchronized (internedStringLock) {
// ...
}
}
根據(jù)Javadocs,intern方法為我們獲得了String對(duì)象的規(guī)范表示。換句話說,intern方法從池中返回一個(gè)String--如果它不在池中,則明確地將它添加到池中--它的內(nèi)容與這個(gè)String相同。
因此,在可重用對(duì)象上的同步問題對(duì)于內(nèi)部的String對(duì)象也是存在的。
注意:所有的String字面符號(hào)和以字符串為值的常量表達(dá)式都是自動(dòng)實(shí)現(xiàn)的。
2.正確用法
為了避免在String字面上進(jìn)行同步的不良做法,建議使用new關(guān)鍵字創(chuàng)建一個(gè)新的String實(shí)例。
讓我們?cè)谝呀?jīng)討論過的代碼中解決這個(gè)問題。首先,我們將創(chuàng)建一個(gè)新的String對(duì)象,以擁有一個(gè)唯一的引用(避免任何重復(fù)使用)和它自己的內(nèi)在鎖,這有助于同步。
然后,我們保持該對(duì)象的private和final,以防止任何外部/不受信任的代碼訪問它。
private final String stringLock = new String("LOCK_STRING");
public void stringSolution() {
synchronized (stringLock) {
// ...
}
}
Boolean 字面量
Boolean類型有兩個(gè)值,即true和false,不適合用于鎖定目的。與JVM中的String字面量類似,boolean字面量也共享Boolean類的唯一實(shí)例。
讓我們來看看一個(gè)在Boolean鎖對(duì)象上同步的錯(cuò)誤用法例子。
private final Boolean booleanLock = Boolean.FALSE;
public void booleanBadPractice() {
synchronized (booleanLock) {
// ...
}
}
在這里,如果任何外部代碼也在具有相同值的Boolean字面上進(jìn)行同步,系統(tǒng)就會(huì)變得沒有反應(yīng),或者導(dǎo)致死鎖的情況。
因此,我們不建議使用Boolean對(duì)象作為同步鎖。
原始類型的包裝類
1. 錯(cuò)誤用法
與boolean字段類似,原始類型的包裝類可能會(huì)重復(fù)使用某些值的實(shí)例。原因是JVM會(huì)緩存和共享可以表示為字節(jié)的值。
例如,讓我們寫一個(gè)在 Integer 上進(jìn)行同步的錯(cuò)誤用法例子。
private int count = 0;
private final Integer intLock = count;
public void boxedPrimitiveBadPractice() {
synchronized (intLock) {
count++;
// ...
}
}
2.正確用法
然而,與boolean字面量不同,在原始類型的包裝類上同步的解決方案是創(chuàng)建一個(gè)新實(shí)例。
與String對(duì)象類似,我們應(yīng)該使用new關(guān)鍵字來創(chuàng)建一個(gè)唯一的Integer對(duì)象的實(shí)例,該實(shí)例有自己的內(nèi)在鎖,并保持其private和final。
private int count = 0;
private final Integer intLock = new Integer(count);
public void boxedPrimitiveSolution() {
synchronized (intLock) {
count++;
// ...
}
}
類同步
當(dāng)一個(gè)類用this關(guān)鍵字實(shí)現(xiàn)方法同步或塊同步時(shí),JVM使用對(duì)象本身作為監(jiān)視器(其固有鎖)。
不受信任的代碼可以獲得并無限期地持有一個(gè)可訪問類的內(nèi)在鎖。因此,這可能會(huì)導(dǎo)致死鎖的情況。
1.錯(cuò)誤用法
例如,讓我們創(chuàng)建Animal類,它有一個(gè)synchronized方法setName和一個(gè)帶有synchronized塊的方法setOwner。
public class Animal {
private String name;
private String owner;
// getters and constructors
public synchronized void setName(String name) {
this.name = name;
}
public void setOwner(String owner) {
synchronized (this) {
this.owner = owner;
}
}
}
現(xiàn)在,讓我們寫一些錯(cuò)誤用法,創(chuàng)建一個(gè)Animal類的實(shí)例,并對(duì)其進(jìn)行同步。
Animal animalObj = new Animal("Tommy", "John");
synchronized (animalObj) {
while(true) {
Thread.sleep(Integer.MAX_VALUE);
}
}
在這里,不受信任的代碼例子引入了一個(gè)無限期的延遲,阻止了setName和setOwner方法的實(shí)現(xiàn)獲得同一個(gè)鎖。
2.正確用法
防止這個(gè)漏洞的解決方案是私人鎖對(duì)象。
我們的想法是使用與我們類中定義的Object類的private final實(shí)例相關(guān)的內(nèi)在鎖來代替對(duì)象本身的內(nèi)在鎖。
另外,我們應(yīng)該使用塊同步來代替方法同步,以增加靈活性,使非同步的代碼不在塊中。
所以,讓我們對(duì)我們的Animal類進(jìn)行必要的修改。
public class Animal {
// ...
private final Object objLock1 = new Object();
private final Object objLock2 = new Object();
public void setName(String name) {
synchronized (objLock1) {
this.name = name;
}
}
public void setOwner(String owner) {
synchronized (objLock2) {
this.owner = owner;
}
}
}
在這里,為了提高并發(fā)性,我們通過定義多個(gè)private final鎖對(duì)象來細(xì)化鎖定方案,以分離我們對(duì)兩個(gè)方法--setName和setOwner的同步關(guān)注。
此外,如果實(shí)現(xiàn)同步塊的方法修改了一個(gè)靜態(tài)變量,我們必須通過鎖定靜態(tài)對(duì)象來實(shí)現(xiàn)同步。
private static int staticCount = 0;
private static final Object staticObjLock = new Object();
public void staticVariableSolution() {
synchronized (staticObjLock) {
count++;
// ...
}
}
總結(jié)
在這篇文章中,我們討論了一些與某些類型的同步有關(guān)的壞做法,如String、Boolean、Integer和Object。
本文最重要的啟示是,不建議使用池化或可重復(fù)使用的對(duì)象進(jìn)行同步。
另外,建議在Object類的private final實(shí)例上進(jìn)行同步。這樣的對(duì)象將無法被外部/不被信任的代碼訪問,否則這些代碼可能會(huì)與我們的公共類交互,從而減少這種交互導(dǎo)致死鎖的可能性。
-
多線程
+關(guān)注
關(guān)注
0文章
277瀏覽量
19899 -
代碼
+關(guān)注
關(guān)注
30文章
4722瀏覽量
68236 -
string
+關(guān)注
關(guān)注
0文章
40瀏覽量
4715
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論