synchronized 在我們平常工作中也是挺常用的, 對于擺脫多線程問題很有幫助。但是如果synchronized被錯誤使用時,可能會給我們帶來很多麻煩。
在本文中,我們將討論與同步相關的一些不好的做法,以及針對每個使用情況的更好的方法。
同步的原則
一般來說,我們應該只對那些我們確信沒有外部代碼會鎖定的對象進行同步。
換句話說,使用池化或可重復使用的對象進行同步是一種不好的做法。原因是池化/可重用對象可以被JVM中的其他進程訪問,外部/不被信任的代碼對這些對象的任何修改都會導致死鎖和非確定性行為。
現在,讓我們來討論基于某些類型的同步原則,如String、Boolean、Integer和Object。
String 字面量
1.錯誤用法
字符串字面量是有池的,在Java中經常被重復使用。因此,不建議使用String類型與 synchronized關鍵字進行同步。
public void stringBadPractice1() {
String stringLock = "LOCK_STRING";
synchronized (stringLock) {
// ...
}
}
同樣地,如果我們使用private final String字面,它仍然是從常量池中引用的。
private final String stringLock = "LOCK_STRING";
public void stringBadPractice2() {
synchronized (stringLock) {
// ...
}
}
此外,為了同步,內接字符串被認為是不好的做法。
private final String internedStringLock = new String("LOCK_STRING").intern();
public void stringBadPractice3() {
synchronized (internedStringLock) {
// ...
}
}
根據Javadocs,intern方法為我們獲得了String對象的規范表示。換句話說,intern方法從池中返回一個String--如果它不在池中,則明確地將它添加到池中--它的內容與這個String相同。
因此,在可重用對象上的同步問題對于內部的String對象也是存在的。
注意:所有的String字面符號和以字符串為值的常量表達式都是自動實現的。
2.正確用法
為了避免在String字面上進行同步的不良做法,建議使用new關鍵字創建一個新的String實例。
讓我們在已經討論過的代碼中解決這個問題。首先,我們將創建一個新的String對象,以擁有一個唯一的引用(避免任何重復使用)和它自己的內在鎖,這有助于同步。
然后,我們保持該對象的private和final,以防止任何外部/不受信任的代碼訪問它。
private final String stringLock = new String("LOCK_STRING");
public void stringSolution() {
synchronized (stringLock) {
// ...
}
}
Boolean 字面量
Boolean類型有兩個值,即true和false,不適合用于鎖定目的。與JVM中的String字面量類似,boolean字面量也共享Boolean類的唯一實例。
讓我們來看看一個在Boolean鎖對象上同步的錯誤用法例子。
private final Boolean booleanLock = Boolean.FALSE;
public void booleanBadPractice() {
synchronized (booleanLock) {
// ...
}
}
在這里,如果任何外部代碼也在具有相同值的Boolean字面上進行同步,系統就會變得沒有反應,或者導致死鎖的情況。
因此,我們不建議使用Boolean對象作為同步鎖。
原始類型的包裝類
1. 錯誤用法
與boolean字段類似,原始類型的包裝類可能會重復使用某些值的實例。原因是JVM會緩存和共享可以表示為字節的值。
例如,讓我們寫一個在 Integer 上進行同步的錯誤用法例子。
private int count = 0;
private final Integer intLock = count;
public void boxedPrimitiveBadPractice() {
synchronized (intLock) {
count++;
// ...
}
}
2.正確用法
然而,與boolean字面量不同,在原始類型的包裝類上同步的解決方案是創建一個新實例。
與String對象類似,我們應該使用new關鍵字來創建一個唯一的Integer對象的實例,該實例有自己的內在鎖,并保持其private和final。
private int count = 0;
private final Integer intLock = new Integer(count);
public void boxedPrimitiveSolution() {
synchronized (intLock) {
count++;
// ...
}
}
類同步
當一個類用this關鍵字實現方法同步或塊同步時,JVM使用對象本身作為監視器(其固有鎖)。
不受信任的代碼可以獲得并無限期地持有一個可訪問類的內在鎖。因此,這可能會導致死鎖的情況。
1.錯誤用法
例如,讓我們創建Animal類,它有一個synchronized方法setName和一個帶有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;
}
}
}
現在,讓我們寫一些錯誤用法,創建一個Animal類的實例,并對其進行同步。
Animal animalObj = new Animal("Tommy", "John");
synchronized (animalObj) {
while(true) {
Thread.sleep(Integer.MAX_VALUE);
}
}
在這里,不受信任的代碼例子引入了一個無限期的延遲,阻止了setName和setOwner方法的實現獲得同一個鎖。
2.正確用法
防止這個漏洞的解決方案是私人鎖對象。
我們的想法是使用與我們類中定義的Object類的private final實例相關的內在鎖來代替對象本身的內在鎖。
另外,我們應該使用塊同步來代替方法同步,以增加靈活性,使非同步的代碼不在塊中。
所以,讓我們對我們的Animal類進行必要的修改。
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;
}
}
}
在這里,為了提高并發性,我們通過定義多個private final鎖對象來細化鎖定方案,以分離我們對兩個方法--setName和setOwner的同步關注。
此外,如果實現同步塊的方法修改了一個靜態變量,我們必須通過鎖定靜態對象來實現同步。
private static int staticCount = 0;
private static final Object staticObjLock = new Object();
public void staticVariableSolution() {
synchronized (staticObjLock) {
count++;
// ...
}
}
總結
在這篇文章中,我們討論了一些與某些類型的同步有關的壞做法,如String、Boolean、Integer和Object。
本文最重要的啟示是,不建議使用池化或可重復使用的對象進行同步。
另外,建議在Object類的private final實例上進行同步。這樣的對象將無法被外部/不被信任的代碼訪問,否則這些代碼可能會與我們的公共類交互,從而減少這種交互導致死鎖的可能性。
-
多線程
+關注
關注
0文章
278瀏覽量
20075 -
代碼
+關注
關注
30文章
4828瀏覽量
69055 -
string
+關注
關注
0文章
40瀏覽量
4748
發布評論請先 登錄
相關推薦
掌握find命令的這幾種用法,就沒有找不到的文件!
常見的幾種日期對象用法
![常見的<b class='flag-5'>幾種</b>日期對象<b class='flag-5'>用法</b>](https://file1.elecfans.com/web2/M00/A7/C0/wKgZomUQ-eaATxwbAAAghzw9DRo351.jpg)
Synchronized multi-spark modul
![<b class='flag-5'>Synchronized</b> multi-spark modul](https://file1.elecfans.com//web2/M00/A5/6B/wKgZomUMOFuAb4KBAAAmReWIyew589.gif)
關于緩存的四大誤用,你中招了嗎?
![關于緩存的四大<b class='flag-5'>誤用</b>,你中招了嗎?](https://file.elecfans.com/web1/M00/55/FA/o4YBAFs4OAqAeoWnAAANwk9UkWE514.jpg)
Java并發編程中線程同步的常用手段synchronized用法
![Java并發編程中線程同步的常用手段<b class='flag-5'>synchronized</b><b class='flag-5'>用法</b>](https://file.elecfans.com/web1/M00/E7/28/o4YBAGBf-XSANHSpAAACgsxQ8kU540.png)
講“伏秒平衡”,驗證磁性元件的錯誤用法資料下載
![講“伏秒平衡”,驗證磁性元件的<b class='flag-5'>錯誤用法</b>資料下載](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
詳細介紹synchronized和Object的關鍵方法和虛擬機實現原理
synchronized知識合集2
synchronized的原理與四種用法介紹
![<b class='flag-5'>synchronized</b>的原理與四種<b class='flag-5'>用法</b>介紹](https://file1.elecfans.com/web2/M00/89/6A/wKgaomSC3w6AZkkgAAB3fRKkV7A861.jpg)
synchronized的鎖膨脹
![<b class='flag-5'>synchronized</b>的鎖膨脹](https://file1.elecfans.com/web2/M00/A7/97/wKgaomUlEguAMlcvAABStEgnAYI194.jpg)
評論