Java Synchronized同步不正確的用法
1.概述
Java中的同步對於擺脫多線程問題很有幫助。但是,如果不仔細使用同步原理,可能會給我們帶來很多麻煩。
在本教程中,我們將討論與同步相關的一些不良做法以及每種用例的更好方法。
2.同步原理
通常,我們應該僅在確定沒有外部代碼鎖定的對像上進行同步。
換句話說,使用池化或可重用的對象進行同步是一種不好的做法。原因是JVM中的其他進程可以訪問池化/可重用的對象,並且外部/不受信任的代碼對此類對象進行的任何修改都可能導致死鎖和不確定性行為。
現在,讓我們討論基於某些類型的同步原理,例如String
, Boolean
, Integer
和Object
。
3. String
字符串
3.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) {
// ...
}
}
此外,它被認為是不好的做法, intern
的String
進行同步:
private final String internedStringLock = new String("LOCK_STRING").intern();
public void stringBadPractice3() {
synchronized (internedStringLock) {
// ...
}
}
根據Javadocs , intern
方法為我們提供String
對象的規範表示。換句話說, intern
方法從池中返回一個String
,並將其顯式添加到池中(如果不存在),其內容與此String
相同。
因此,對於可重複使用的對象,同步問題對於持久化的String
像也仍然存在。
注意:所有String
文字和字符串值常量表達式都將自動被interned 。
3.2 解決辦法
建議避免對String
文字進行同步的不良做法,建議使用new
關鍵字創建String
的新實例。
讓我們用已經討論過的代碼解決問題。首先,我們將創建一個新的String
對象,使其具有唯一的引用(以避免任何重用)和它自己的固有鎖定,這有助於同步。
然後,我們將對象保持private
和final
以防止任何外部/不受信任的代碼訪問它:
private final String stringLock = new String("LOCK_STRING");
public void stringSolution() {
synchronized (stringLock) {
// ...
}
}
4. Boolean
類型
具有兩個值true
和false
的Boolean
類型不適用於鎖定目的。類似於JVM中的String
文字, boolean
文字值還共享Boolean
類的唯一實例。
讓我們看一個錯誤的代碼示例,該示例在Boolean
鎖定對像上進行同步:
private final Boolean booleanLock = Boolean.FALSE;
public void booleanBadPractice() {
synchronized (booleanLock) {
// ...
}
}
在這裡,如果任何外部代碼也在具有相同值的Boolean
文字上同步,則係統可能變得無響應或導致死鎖情況。
因此,我們不建議將Boolean
對像用作同步鎖。
5.裝箱基本類型
5.1。不良做法
類似於boolean
文字,裝箱的類型可能會將實例重用於某些值。原因是JVM緩存並共享可以表示為字節的值。
例如,讓我們寫一個糟糕的代碼示例,對盒裝類型Integer
同步:
private int count = 0;
private final Integer intLock = count;
public void boxedPrimitiveBadPractice() {
synchronized (intLock) {
count++;
// ...
}
}
5.2 解決辦法
但是,與boolean
文字不同,對盒裝原語進行同步的解決方案是創建一個新實例。
與String
像類似,我們應該使用new
關鍵字創建具有自己的內部鎖的Integer
對象的唯一實例,並將其保持private
和final
:
private int count = 0;
private final Integer intLock = new Integer(count);
public void boxedPrimitiveSolution() {
synchronized (intLock) {
count++;
// ...
}
}
6.this同步
當類使用this
關鍵字實現方法同步或阻止同步時,JVM將對象本身用作監視器(其內部鎖)。
不受信任的代碼可以獲取並無限期持有可訪問類的內部鎖。因此,這可能導致死鎖情況。
6.1。不良做法
例如,讓我們使用synchronized
方法setName
和帶有synchronized
塊的方法setOwner
創建Animal
類:
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
方法的實現獲取相同的鎖。
6.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
同步關注。
此外,如果實現synchronized
塊的方法修改了static
變量,則必須通過鎖定static
對象來進行同步:
private static int staticCount = 0;
private static final Object staticObjLock = new Object();
public void staticVariableSolution() {
synchronized (staticObjLock) {
count++;
// ...
}
}
7.結論
在本文中,我們討論了與某些類型(如String
, Boolean
, Integer
和Object
上的同步相關的一些不良做法。
本文最重要的要點是,不建議使用池化或可重用的對象進行同步。
另外,建議在Object
類的private final
實例上進行同步。外部/不受信任的代碼將無法訪問此對象,否則這些代碼可能會與我們的public
類進行交互,從而減少了此類交互可能導致死鎖的可能性。