2010-06-23 26 views
10

在一個很好的article with some concurrency tips,一個例子進行了優化,以下面的行:差分讀取和揮發性

double getBalance() { 
    Account acct = verify(name, password); 
    synchronized(acct) { return acct.balance; } 
} 

如果我明白正確,同步點是保證的值此線程讀取的acct.balance是當前值,並且任何等待寫入acct.balance中的對象字段的寫入也會寫入主內存。

這個例子讓我想起了一點:僅僅將acct.balance(即類Account的字段餘額)聲明爲volatile不是更高效嗎?它應該更有效率,在訪問acct.balance時節省所有的synchronize,並且不會鎖定整個acct對象。我錯過了什麼嗎?

+0

你是正確的,但文章實際上是關於完全不同的東西 - 減少鎖的範圍。 – gustafc 2010-06-23 17:58:22

回答

13

你是對的。 volatile提供了可見性保證。同步提供了受保護代碼段的可視性保證和序列化。對於非常簡單的情況,volatile是足夠的,但是使用volatile而不是同步很容易陷入麻煩。

如果你假定賬戶有調整其資產負債的一種方式則揮發性不夠好

public void add(double amount) 
{ 
    balance = balance + amount; 
} 

然後我們有一個問題,如果餘額爲揮發性沒有其他的同步。如果兩個線程都試圖調用add()在一起,你可以有一個「失敗」的更新,其中將出現以下情況

Thread1 - Calls add(100) 
Thread2 - Calls add(200) 
Thread1 - Read balance (0) 
Thread2 - Read balance (0) 
Thread1 - Compute new balance (0+100=100) 
Thread2 - Compute new balance (0+200=200) 
Thread1 - Write balance = 100 
Thread2 - Write balance = 200 (WRONG!) 

這顯然是錯誤的,因爲兩個線程讀取電流值,並單獨更新,然後寫回(讀,計算,寫)。 volatile在這裏沒有幫助,所以你需要同步以確保一個線程在另一個線程開始之前完成整個更新。

我總體發現,如果在編寫代碼時我認爲「我可以使用volatile而不是synchronized」,答案很可能是「是」,但是確定它的時間/努力以及弄錯它的危險不值得的好處(次要表現)。

另外一個寫得很好的賬戶類將在內部處理所有的同步邏輯,因此呼叫者不必擔心它。

1

聲明帳戶作爲揮發性受到以下問題和限制

1.「由於其他線程無法看到局部變量,聲明局部變量波動是徒勞。」此外,如果您嘗試在方法中聲明易失性變量,則在某些情況下會出現編譯器錯誤。

double getBalance(){ volatile賬戶acct = verify(name,password); //錯誤.. }

  • 聲明帳戶作爲揮發性警告編譯器獲取它們新鮮每一次,而不是在寄存器緩存它們。這也禁止某些優化,假設沒有其他線程會意外地更改值。

  • 如果您需要同步協調來自不同的線程修改變量, 揮發並不能保證你原子訪問,因爲訪問volatile變量從未持有鎖,它不適合我們想要的情況下以讀取更新 - 寫入爲原子操作。除非你確定acct = verify(name,password);是單原子操作,你不能保證例外結果

  • 如果變量acct是一個對象引用,那麼它的機會可能是null。 試圖在空對象上進行同步將引發使用同步的NullPointerException。 (因爲你有效地同步的參考,而不是實際的對象) 凡爲揮發性不抱怨

  • 相反,你可以聲明一個布爾變量揮發性喜歡這裏

    私人揮發性布爾someAccountflag;

    public void getBalance(){ Account acct; (!someAccountflag){ acct = verify(name,password); } }

  • 注意不能聲明someAccountflag爲synchronized,因爲 你不能在原語同步同步,同步僅適用於對象變量,其中作爲原始或對象變量可以被聲明爲volatile

    6. 類最終靜態字段不需要是易失的,JVM負責處理這個問題。所以someAccount標誌不需要甚至被聲明爲易變的,如果它是最終的靜態的 或者你可以使用懶惰單例初始化使得Account作爲單例對象 並聲明如下: 私人最終靜態AccountSingleton acc_singleton = new AccountSingleton();

    +0

    謝謝你的回答,但我實際上建議不要將acct聲明爲volatile,而是acct.balance - 也就是類Account的字段餘額。這會修改你的一些評論。我試圖在這方面澄清這個問題。 – 2010-08-23 10:07:16

    1

    如果多個線程正在修改和訪問數據,​​保證多個線程之間的數據一致性。

    如果單線程正在修改數據並且多個線程嘗試讀取數據的最新值,請使用volatile構造。

    但是對於上面的代碼,volatile不保證內存一致性,如果多個threds修改餘額。 AtomicReferenceDouble類型服務於您的目的。

    相關SE問題:

    Difference between volatile and synchronized in Java