2012-12-05 63 views
1

我讀this計算器主題,結論似乎是一個空​​塊可以總是一個更好的解決方案來避免。這個話題對我來說也有一些不清楚的部分,我將整合到我的下面的文章中。空同步塊:數據可視性

假設我們有一類這樣的:

public class MyThreadClass extends Thread { 
    private final OtherComponent mOtherComponent; 
    private final Object mLock = new Object(); 
    private MyHandler mHandler; 

    public MyCustomThread(OtherComponent otherComponent) { 
     mOtherComponent = otherComponent;  

    public void run() { 

     mHandler = new Handler() {...} 

     // Other init operations 

     mOtherComponent.onMyCustomThreadInitialized(); 

     // ... other operations (Looper.loop(), actually) 
    } 

    public void sendMessageWithHandler(int what, int arg) { 
     synchronized (mLock) {} 
     Message msg = mHandler.obtainMessage(what); 
     msg.arg1 = arg; 
     mHandler.sendMessage(msg); 
    } 

    public void useHandlerInAnotherWay(...) { 
     synchronized (mLock) { 
      // useful code that needs actual mutual exclusion 
     } 
     mHandler.sendMessage(...); 
    } 
} 

我的應用程序的相關部分工作在以下方式:

  1. MyThreadClass線程創建並啓動。
  2. 作爲mOtherComponent.onMyCustomThreadInitialized()的間接後果,我的應用程序的其它部分將開始產卵其他線程。 (請注意,不是從這個電話同步啓動,這就是爲什麼我說這是間接的後果。的唯一一點是,mHandler已經由時初始化這些其他線程啓動
  3. 每其他線程將恰好一次
  4. 又一次打電話sendMessageWithHandler(...)其他線程(即不是上面提到的線程)調用useHandlerInAnotherWay(...),這可能發生在任何時間(mOtherComponent.onMyCustomThreadInitialized()後,當然)。

我的問題:

  1. 如果我是正確的,先進的年月日時mHandler從其它線程訪問比myThreadClass數據可見性必須得到保證,因爲它不是一個final場。我不想讓它volatile要麼,因爲除了這幾個sendMessageWithHandler(..)電話,mHandler不能從其他線程同步不使用(我不希望volatile開銷存在不必要的地方是不必要的)。換句話說,當從mHandler那些另外的其他線程經由useHandlerInAnotherWay()被訪問時,與該「有用的代碼」(即,實際上需要是相互排斥的受試者碼)​​那裏也保證了呼叫者線程看到mHandler正確。在sendMessageWithHandler(..),然而,代碼不需要相互排斥,所以我決定把一個空同步塊的sendMessageWithHandler(...)開始。它是否正確?我的問題有更好的解決方案嗎?

  2. 其他計算器線程我掛有如下的答案(它不是接受一個,但upvoted多次):

    它曾經是說明書暗示某些內存 屏障的情況下操作發生。但是,規範現在已經改變,並且原始規範從未正確實施。它可以用來等待 另一個線程釋放鎖,但協調的 其它線程已經獲得鎖將是棘手的。

    這是否意味着空的​​不再提供內存屏障功能?如果我在線查看關於​​的Java文檔,他們會提到所有內存都會因爲它而更新(即線程副本從顯示器輸入處的「主內存」更新,並且「主內存」從顯示器出口處的線程副本更新)。但他們沒有提及任何關於空的​​塊,所以這對我來說還不清楚。

+1

查看http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html。也許信號量或障礙對你的用例更好。 – ssedano

+0

感謝您的反饋。我的軟件非常複雜,部分內容沒有在這裏列出,所以請讓我們假設我需要通用我所問的功能。我開發的組件必須比Semaphore或CycleBarrier提供的更通用。 –

+0

如果您也意味着我的聲明「每個其他線程只調用一次sendMessageWithHandler *」,那麼讓我們忽略「恰好一次」。只是爲了強調該方法不經常使用,因此線程無法獲得自己的mHandler副本是沒有意義的。 –

回答

2

您可能不需要任何同步。

read保證看到write


的​​語義是嚴格執行,即使它是一個空塊。

+0

謝謝。就計算機內發生的事情而言,這是否意味着*線程2 * **不能擁有**過時的mHandler副本,因爲它以前從未訪問過它,對吧?換句話說,如果我的軟件保證(以某種方式,例如通過更高級別的構造),**首先從另一個線程讀取非最終非易失性變量**在**最後寫入**之後**對那個變量,我安全嗎? –

+0

「高級構造」=例如在這種情況下,這意味着線程2在*最後寫入*到mHandler後啓動。這可以推廣嗎?例如。如果線程2可能在* last寫入* mHandler之前啓動,但是應用程序中的某些約束保證它只會在*上次寫入*之後纔會讀取它。在這種情況下,我想我也是安全的。 (當然,健壯和可維護的代碼也很重要,我知道。) –

+0

它取決於'after'的定義。 Java內存模型定義了可以被認爲是正式的「之前/之後」關係的關係之前的同步和發生之間的關係。我的圖中的箭頭符合這個關係,所以讀取是在'寫入'之後。請參閱http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.4 – irreputable

1

正如我所看到的,mHandler在創建之前不會被其他線程訪問,並且在生命週期中不會更改。因此,在沒有任何同步的情況下從其他線程讀取它是安全的。是絕對肯定的是,你可以同步塊中閱讀:

public void sendMessageWithHandler(int what, int arg) { 
    MyHandler mHandler; 
    synchronized (mLock) { 
     mHandler=this.mHandler; 
    } 
    // the rest of code unchanged 
} 

由於「其他每個線程會調用sendMessageWithHandler(...)恰好一次」,開銷是絕對可以忽略不計。坦率地說,你最大限度地減少同步設施的使用(「我不想讓它變得不穩定」)在多個方法調用和線程創建的背景下看起來不夠充分。僅當同步發生數百萬次/秒時才值得困擾。

+0

謝謝,這是關於性能的一個很好的觀點。我只是不喜歡我的代碼中的冗餘元素(如volatile + synchronized),它們會降低我的可讀性。 –

+0

...但在代碼更改的情況下提高可靠性。他們還考慮如何通過最小化線程交互和最大化使用本地線程內存來安全地消除它們,這可以提高性能。 –

+0

這確實是「永恆」的問題:計劃什麼和未來計劃什麼(「改變設計」)。對於初學者來說,你可能會提出多餘的揮發性物質的建議,但在這個具體項目中,我知道潛在未來變化的性質,在這個具體情況下,用不必要的揮發性物質污染代碼並不是一個好的解決方案。 –