思維導圖:
本文主要介紹類的域變數被多個執行緒共享時所導致的可見性問題。我所理解的可見性是指類的域變數在某一線程中變化時能夠及時準確的被其他執行緒所看見。文章還是分成兩個部分進行描述:
理論部分:包括如何判斷域變數是否可見,會導致什麼樣的問題,又該如何解決。
使用部分:通過 不發布物件》發布但不可修改》安全發布物件的次序逐漸深入的**如何構建乙個執行緒安全的類。
同步操作不僅可以實現操作的原子性,還可以保證記憶體可見性。即執行緒在修改狀態後,狀態的改變對其他執行緒是可見的。在這一小節中,我們會介紹如何判斷類的域變數是否可見,會導致什麼問題,又該如何解決的理論方法。
發布的定義是使物件能夠在當前作用域之外的**起作用。也就是說發布的物件能夠被其他執行緒所看到,但是已發布物件的變換可能並不會被其他執行緒所觀測到。
判斷乙個物件是否被發布常用以下四種手段:
public class thisescape
});}
}
像上述第四點一樣,本來不想被發布的物件在無意間被發布的情況我們稱之為逸出;
發布後的物件在多執行緒的環境下會發生一些平時在單執行緒環境中意想不到的錯誤。比如現在兩個執行緒a,b,他們同時使用了已發布的物件x,y。
一種常見的錯誤稱為失效資料,描述如下:當a執行緒修改了物件x和y後,b執行緒開始讀取x,y的值,發現他們的值還是a執行緒修改以前的值,這是b執行緒所觀測的x,y的值其實是失效的,所以稱之為失效資料。
另一種常見的錯誤稱之為指令重排,指令重排其實是jvm優化**的一種手段,但是這回導致這樣的併發問題:a執行緒修改x後,又修改了y,但是b執行緒在讀取x和y的值時發現,只有y被修改了,x沒變。這是因為jvm優化時進行指令重排先修改了y,在修改x,而x的改變有沒有被b執行緒所觀測到所導致的。
例如以下**:number和ready在改變後,可能被另一線程觀測到輸出42,也可能由於number的改變沒有被觀測到輸出0,還有可能在迴圈使number的值其實已經是42了,但是一直在迴圈。
public class novisibility
}public static void main(string args)
}
保證發布物件的可見性常用兩種辦法
@threadsafe
public class synchronizedinteger
public synchronized void set(int value)
}
@threadsafe
public class synchronizedinteger }}
在這個小節的最後,保證類的執行緒安全性除了保證其域變數的可見性。我們還可以通過保證域變數的不可見或者域變數的不可變來實現。
所謂的執行緒封閉是指只在單執行緒內訪問資料,其他執行緒是不可見的,所以變數也不需要進行同步。
ad-hoc執行緒封閉指單純的在**邏輯上實現執行緒封閉,即維護執行緒封閉性的職責完全由程式實現來承擔。ad-hoc執行緒封閉非常脆弱,沒有語言特性和編譯器檢查進行支援,所以盡量不要使用ad-hoc執行緒封閉。
棧封閉即是指只能通過區域性變數才能訪問物件,而區域性變數的固有屬性就是封閉在執行執行緒中(jvm的虛擬機器棧),其他執行緒無法訪問。其實就是在方法內部定義變數進行使用,不要使物件逸出即可。
一種使用執行緒封閉技術更加規範的方式是使用threadlocal類。threadlocal類可以使執行緒中的某個值和儲存值得物件關聯起來。每個執行緒都擁有該變數的乙個副本,所以threadlocal裡的變數是執行緒封閉的。如下**,我們將jdbc連線儲存到threadlocal中,那麼每個執行緒都可以擁有屬於自己的連線:
public class connectiondispenser catch (sqlexception e) }};
public connection getconnection()
}
如果我們必須發布某個物件的話,那麼可以考慮發布乙個不會改變的物件以保證執行緒安全性,這被稱之為不變性。
我們可以直接構造乙個永遠不可改變的物件,如下**所示:
@immutable
public final class threestooges
public boolean isstooge(string name)
}
如上所述的不可變物件一定是執行緒安全的,一般來說不可變物件具有如下幾個特性:
雖然不可變物件是執行緒安全的,但是指向不可變物件的引用則不一定是執行緒安全的,所以需要使用volatile關鍵字修飾不可變物件以保證執行緒安全性。在下列**中,我們先構建乙個儲存因式分解的數及其結果的不可變物件,在使用volatile修飾以發布乙個執行緒安全的類:
@immutable
public class onevaluecache
public biginteger getfactors(biginteger i)
}
@threadsafe
public class volatilecachedfactorizer extends genericservlet implements servlet
encodeintoresponse(resp, factors);}}
在前兩節中我們描述的都是如何不發布或者發布不能改變的物件以保證執行緒安全性。終於,在這一小節中,我們還是不得不發布任何執行緒都可以修改的物件了。
一般的,要安全的發布引用必須保證物件的引用即物件的狀態同時對其他執行緒可見,如下四種方式用於發布物件是安全的:
java執行緒學習 2 物件的共享
編寫安全的執行緒就是對共享資料的操作不出現問題,通過同步使來避免多個執行緒同一時刻方位共享資料,或者共享和發布物件,從而使能夠安全地由多個執行緒同時訪問。可見性 public class novisibility public static void main string args 這段 不推薦這...
3物件的共享
作用 將當前執行緒對volatile的改變立即通知給其他執行緒 保證了volatile變數對執行緒的可見性 volatile是一種比synchronizyed稍弱的同步機制 對可見性的影響 volatile變數對可見性的影響比volatile變數本身更為重要。當執行緒a首先寫入乙個volatile變...
python學習筆記2(物件)
物件的屬性不能繼承。方法可以呼叫沒有的引數或方法,但執行時會報錯。property setter標記的方法可以當作屬性來用。class animal object def init self self.age yi def run self print self.name,is running cl...