標籤:訪問 靜態 ber 調用 tle 編譯 ini 通過 final
這章的主要內容是:如何共用和發布對象,從而使它們能夠安全地由多個線程同時訪問。
記憶體的可見度
確保當一個線程修改了對象狀態後,其他線程能夠看到發生的狀態變化。
上面的程式中NoVisibility可能會持續迴圈下去,因為讀線程可能永遠都看不到ready的值。一種更奇怪的現象是NoVisibility可能會輸出0,因為讀線程可能看到了寫入ready的值,但卻沒有看到之後寫入number的值,這種現象被稱為“重排序”。多線程之指令重排序
失效資料
簡而言之就是在缺乏同步的程式中可能會讀取到到期的資料,也就是失效資料,就像上面的例子一樣,當度線程查看ready變數時可能會的得到一個失效的值。
非原子的64位操作
雖然得到的可能是一個失效值,但至少這個值是由之前每個線程設定的,而不是一個隨機值。這種安全性保證也被稱為最低安全性。最低安全性適用於絕大多數變數,但是對於非volatile類型的64位元值變數(double和long)並非如此。java變數的讀操作和寫操作都是原子的,但是JVM允許將64位的讀操作和寫操作分解為2個32為的操作。這樣當讀取一個非volatile的long變數時,如果對該變數的讀操作和寫操作在不同的線程中執行,那麼很可能會讀取到某個值的高32為和另一個值的低32位。
加鎖與可見度
Volatile變數
確保將變數的更新操作通知到其他線程,當把變數聲明為volatile類型後,編譯器與運行時都會注意到這個變數是共用的,因此不會將該變數上的操作與其他記憶體操作一起重排序。在讀取volatile類型的變數時總會返回最新的寫入值。下面的程式給出了volatile變數的一種典型用法:檢查每個狀態標記以判斷是否退出迴圈。
volatile的語義不足以確保遞增操作(count++)的原子性。若且唯若滿足一下所有條件是,才應該使用volatile變數:
1. 對變數的寫入操作不依賴變數的當前值,或者你能確保只有單個線程更新變數的值
2. 該變數不會與其他狀態變數一起納入不變性條件中
3. 在訪問變數時不需要加鎖
Java中Volatile關鍵字詳解&&Java並發編程:volatile關鍵字解析
發布與逸出
“發布”一個對象是指:使對象能夠在當前範圍之外的代碼中使用。當某個不該發布的對象被發布時,這種情況就被稱為逸出。發布對象的最簡單方法是將對象的引用儲存到一個公有的靜態變數中;
安全的物件建構過程
不要在構造過程中使this引用逸出。在構造過程中使this引用逸出的一個常見的錯誤是:在建構函式中啟動一個線程。當對象在其建構函式中建立一個線程時,無論是顯示建立還是隱士建立,this引用都會被新建立的線程共用。(簡而言之在對象的建構函式中的別的對象能夠拿到當前對象的this引用從而造成逸出)。
線程封閉
不共用資料,僅在單線程內訪問資料,避免使用同步的方式。這種技術被大量使用喻Swing和JDBC的Connection對象。
Ad-hoc線程封閉
指維護線程封閉性的職責完全由程式實現來承擔。很脆弱,盡量使用更強的線程封閉技術(如棧封閉或ThreadLocal類)
棧封閉
只能通過局部變數才能訪問對象。局部變數的固有屬性之一就是封閉在執行線程中。他們位於執行線程的棧中,其他線程無法訪問這個棧。
ThreadLocal類
這個類能使線程中的某個值與儲存值的對象關聯起來。ThisLocal提供了get和set等提供者或方法,當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的線程提供獨立的變數副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。ThreadLocal對象通常用於防止對可變的單一實例或全域變數進行共用。通過將JDBC的串連儲存到ThreadLocal對象中(因為JDBC的連線物件不一定是安全執行緒的),每個線程都會有屬於自己的串連。
當某個頻繁執行的操作需要一個臨時對象,例如緩衝區,同時又希望避免在每次執行時都重新分配該臨時對象,就可以使用這項技術。當某個線程初次調用ThreadLocal.get方法時,救會調用initialValue來擷取初始值。
不變性
滿足同步需求的另一種方法是使用不可變對象,即對象建立以後狀態不能修改,它的所有域都是final類型,並且對象是正確建立的(this引用沒有逸出)。在不可變對象的內部仍可以使用可變對象來管理它們的狀態,如
Final域
final類型的域是不能修改的,但如果final域所引用的對象是可變的(如上例),那麼這些被引用的對象是可以修改的。
樣本:使用volatile類型來發布不可變對象
繼續前面因式分解的例子。因式分解Servlet將執行兩個原子操作:更新緩衝的結果,以及通過判斷緩衝中的數值是否等於請求的數值來決定是否直接讀取緩衝中的因數分解結果。每當需要對一組相關資料以原子方式執行某個操作時,就可以建立一個不可變的類來包含這些資料,如:
對數值及其因數分解結果進行緩衝的不可變容器類
@Immutableclass OneValueCache { private final BigInteger lastNumber; private final BigInteger[] lastFactors; /** * 如果在建構函式中沒有使用 Arrays.copyOf()方法,那麼域內不可變對象 lastFactors卻能被域外代碼改變 * 那麼 OneValueCache 就不是不可變的。 */ public OneValueCache(BigInteger i, BigInteger[] factors) { lastNumber = i; lastFactors = Arrays.copyOf(factors, factors.length); } public BigInteger[] getFactors(BigInteger i) { if (lastNumber == null || !lastNumber.equals(i)) return null; else return Arrays.copyOf(lastFactors, lastFactors.length); }}
安全發布
不正確的發布,正確的對象被破壞
不可變對象與初始化安全性
任何線程都可以子在不需要額外同步的情況下安全地訪問不可變對象(狀態不可變,域都是final類型,正確的構造過程)
安全發布的常用模式
可變對象必須通過安全的方式來發布。
事實不可變對象
本身可變但對象在發布後不會被修改。在沒有額外的同步情況下,任何線程都可以安全地使用被安全發布的事實不可變對象。
可變對象
不僅需要安全發布,而且需要額外的同步和安全執行緒來保護
安全地共用對象
《java並發編程實戰》讀書筆記2--對象的共用,可見度,安全發布,線程封閉,不變性