[英]Empty synchronized block revisited: data visibility
我讀了這個 stackoverflow主題,結論似乎是一個空的synchronized
塊總是可以避免使用更好的解決方案。 這個話題對我來說也有一些不清楚的部分,我將整合到我的下面的帖子中。
假設我們有一個這樣的類:
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(...);
}
}
我的應用程序的相關部分以下列方式工作:
MyThreadClass
線程已創建並啟動。 mOtherComponent.onMyCustomThreadInitialized()
的間接結果,我的應用程序的另一部分將開始產生其他線程 。 (注意它們不是從這個調用同步啟動的,這就是為什么我說這是間接后果。 唯一的一點是, mHandler
已經被這些其他線程啟動時初始化 ) sendMessageWithHandler(...)
一次 useHandlerInAnotherWay(...)
,這可能在任何時候發生mOtherComponent.onMyCustomThreadInitialized()
當然在mOtherComponent.onMyCustomThreadInitialized()
之后)。 我的問題:
如果我是正確的,最多時最新數據可見性必須得到保證mHandler
從其他線程訪問比myThreadClass
,因為它不是一個final
場。 我也不想讓它變得volatile
,因為除了這幾個sendMessageWithHandler(..)
調用之外, mHandler
沒有在沒有同步的情況下從其他線程中使用(我不希望在不必要的地方不必要地存在volatile
開銷)。 換句話說,當mHandler
已經從那些還沒有其他線程通過訪問useHandlerInAnotherWay()
時, synchronized
的“有用的代碼”(即實際需要的是相互排斥的對象,即代碼)存在也保證了呼叫者線程看到mHandler
正確。 但是,在sendMessageWithHandler(..)
,代碼不需要互斥,所以我決定將一個空的synchronized塊放到sendMessageWithHandler(...)
的開頭。 這個對嗎? 我的問題有更好的解決方案嗎?
我鏈接到的另一個stackoverflow線程有以下答案(它不是被接受的,但被多次upvoted):
過去,規范暗示某些內存屏障操作發生。 但是,規范現在已經改變,原始規范從未正確實現。 它可能用於等待另一個線程釋放鎖,但協調另一個線程已經獲得鎖定將是棘手的。
這是否意味着空synchronized
不再提供內存屏障功能? 如果我在線查看關於synchronized
的Java文檔,他們會提到由於它而更新了所有內存(即,在監視器輸入時從“主存儲器”更新線程副本,並且在監視器出口處從“線程副本”更新“主存儲器”)。 但是他們沒有提到關於空synchronized
塊的任何事情,所以我不清楚這一點。
您可能不需要任何同步。
(thread 1) (thread 2)
write
|
start thread 2
\
...
|
read
read
保證看到write
。
即使它是一個空塊,也嚴格執行synchronized
的語義。
正如我所看到的,mHandler在創建之前不會被其他線程訪問,並且在生命周期內不會被更改。 因此,可以安全地從其他線程讀取它而無需任何同步。 要絕對確定,您可以在synchronized塊中讀取它:
public void sendMessageWithHandler(int what, int arg) {
MyHandler mHandler;
synchronized (mLock) {
mHandler=this.mHandler;
}
// the rest of code unchanged
}
由於“其他每個線程都會調用sendMessageWithHandler(...)
一次”,因此開銷幾乎可以忽略不計。 坦率地說,你對最小化同步設施的使用(“我不想讓它變得易失”)的痴迷在多個方法調用和線程創建的背景下看起來不合適。 只有當同步每秒發生數百萬次時才值得打擾。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.