[英]Two threads updating the same object, will it work? java
我有以下兩種方法:
class A{
void method1(){
someObj.setSomeAttribute(true);
someOtherObj.callMethod(someObj);
}
void method2(){
someObj.setSomeAttribute(false);
someOtherObj.callMethod(someObj);
}
}
在另一個地方評估該屬性的位置:
class B{
void callMethod(Foo someObj){
if(someObj.getAttribute()){
//do one thing
} else{
//so another thing
}
}
}
請注意, A.method1
和A.method2
正在更新同一對象的屬性。 如果這 2 個方法在 2 個線程中運行,這會起作用還是會出現意外結果?
會不會有意想不到的結果? 是的,保證,因為如果你修改了你不希望對你的應用程序產生影響的東西(例如月相,你的 winamp 中播放的當前歌曲,你的狗是否在 CPU 附近擁抱,如果它是月的第 5 個星期二,以及其他類似的事情),這可能會對行為產生影響。 你不想要的。
您所描述的是所謂的對 java 內存模型的違反:最終結果是任何 java 實現都可以自由返回多個值中的任何一個,但是,該 VM 根據 java 規范正常運行。 即使它看起來是隨意的。
作為一般規則,每個線程都會得到一個不公平的硬幣。 不公平,因為它會試圖惹你:每次測試時它都會正確翻轉,然后在生產中,只有當你向那個關鍵客戶進行演示時,它才會得到你。
每次它從任何字段讀取或寫入時,它都會翻轉這個平均硬幣。 在頭上,它將使用實際字段。 在尾部,它將使用它制作的本地副本。
這有點過於簡化了模型,但這是嘗試了解其工作原理的良好開端。
出路是強制所謂的“先於”關系:java 會做的是確保您可以觀察到的內容與這些關系匹配:如果事件 A 被定義為具有先於關系與事件 B,那么任何事情A 確實會被觀察到,B 保證完全按照原樣。 沒有更多的硬幣翻轉。
建立優先關系的例子包括使用volatile
、 synchronized
和任何在內部使用這些東西的方法。
注意:當然。 如果您沒有粘貼的setSomeAttribute
方法包含一些先於建立行為,那么這里沒有問題,但通常稱為setX
的方法不會這樣做。
一個沒有的例子:
class Example {
private String attr;
public void setAttr(String attr) {
this.attr = attr;
}
}
一些這樣做的例子:
比方說,法B.callMethod
在同一個線程中執行method1
-那你都保證至少觀察變化做出方法1,但它仍然是一個拋硬幣(不管你實際看到有什么方法2.沒有或沒有)。 不可能在method1 或method2 運行之前看到該屬性的值,因為在單個線程中運行的代碼在整個運行中都出現了(在同一線程中的另一行之前執行的任何行都有一個出現-關系之前)。
set 方法如下所示:
class Example {
private String attr;
private final Object lock = new Object();
public void setAttr(String attr) {
synchronized (lock) {
this.attr = attr;
}
}
public String getAttr() {
synchronized (lock) {
return this.attr;
}
}
}
現在 get 和 set ops 鎖定在同一個對象上,這是建立優先的方法之一。 哪個線程首先獲得鎖是可觀察到的行為; 如果method1的set在B的get之前到達那里,那么你一定會觀察到method1的set。
更一般地說,線程之間共享狀態非常棘手,您應該盡量不要這樣做。 替代方案是:
我假設您期望的是當您調用A.method1
, someObj.getAttribute()
將在B.callMethod
返回true
,當您調用A.method2
, someObj.getAttribute()
將在B.callMethod
返回false
。
不幸的是,這行不通。 因為在setSomeAttribute
和callMethod
行之間,其他線程可能會更改該屬性的值。
如果你只在callMethod
使用屬性,為什么不直接傳遞屬性而不是Foo
對象。 代碼如下:
class A{
void method1(){
someOtherObj.callMethod(true);
}
}
class B{
void callMethod(boolean flag){
if(flag){
//do one thing
} else{
//so another thing
}
}
}
如果必須使用Foo
作為參數,你可以做的就是讓setAttribute
和callMethod
原子化。
實現它最簡單的方法是使其同步。代碼如下:
synchronized void method1(){
someObj.setSomeAttribute(true);
someOtherObj.callMethod(someObj);
}
synchronized void method2(){
someObj.setSomeAttribute(false);
someOtherObj.callMethod(someObj);
}
但這可能會帶來糟糕的性能,您可以使用一些更細粒度的鎖來實現它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.