2017-07-20 206 views
1

volatile關鍵字我有下面的代碼:線程安全

public class Foo { 
    private volatile Map<String, String> map; 

    public Foo() { 
    refresh(); 
    } 

    public void refresh() { 
    map = getData(); 
    } 

    public boolean isPresent(String id) { 
    return map.containsKey(id); 
    } 

    public String getName(String id) { 
    return map.get(id); 
    } 

    private Map<String, String> getData() { 
    // logic 
    } 

} 
  • 是上面的代碼線程安全的或者我需要添加​​或互斥在那裏?如果它不是線程安全的,請說明原因。

而且,我讀過,應該使用AtomicReference,而不是這個,而是在AtomicReference類的來源,我可以看到,用於保存值的字段是揮發性的(少數方便的方法沿)。

  • 是否有具體原因使用AtomicReference代替?

我讀過與此有關的多個答案,但volatile的概念仍然讓我困惑。提前致謝。

+0

你應該使用'ConcurrentHashMap'進行實例化。在這種情況下,volatile(或原子引用)在這裏是無用的。 – dehasi

+0

它不是線程安全的,雖然對Map類的引用是不穩定的,但數據不是 – nafas

+0

@nafas但我從不修改地圖對象本身。我一直在更新參考。 – GurV

回答

2

如果你沒有修改map的內容(創建它時refresh()的內部除外),則代碼中沒有可見性問題。

它仍然可以做到isPresent()refresh()getName()(如果沒有外界的同步使用),並與isPresent()==truegetName()==null結束。

+0

你說得對。這可能發生。你認爲最好的解決方法是什麼?我認爲只是使用'getName()'看看它是否爲空(我永遠不會在名稱中爲null)。 – GurV

+1

一個選擇是將'synchronized'添加到'refresh()',並且每當你做'isPresent()/ getName()'時,你就會在你的'Foo'上同步。但是,由於'getName()'返回'null'(如果不存在),我不確定爲什麼要編寫'isPresent()/ getName()'組合。再說一遍,我不知道你在用你的代碼做什麼。 – Kayaman

+0

如果我省略'isPresent'方法,我還需要同步嗎? – GurV

0

易失性僅在此場景中用於立即更新值。它並不真正使代碼本身是線程安全的。

但是,因爲你已經在您的評論說,你只更新參考,並因爲基準開關是原子,你的代碼將是線程安全的。(從給定的代碼)

0

如果我正確理解您的問題並提出您的意見 - 您的班級Foo保留Map,其中只有參考值應該更新,例如,添加了全新的Map而不是改變它。如果這是前提條件:

如果您將其聲明爲volatile,則沒有任何區別。 Java中的每個讀/寫操作都是原子本身。你將永遠不會看到這些操作的一半交易。請參閱JLS 17.7

17.7。 Non-Atomic Treatment of double and long

出於Java編程語言內存模型的目的,對非易失性long或double值的單次寫入被視爲兩次單獨寫入:每次寫入32位一半。這可能會導致線程看到來自一次寫入的64位值的前32位和來自另一次寫入的第二次32位。

對volatile和double值的寫入和讀取總是原子的。

寫入和讀取引用始終是原子的,無論它們是以32位還是64位值實現的。

某些實現可能會發現將64位long或double值的單個寫入操作劃分爲相鄰32位值的兩個寫入操作很方便。爲了提高效率,此行爲是特定於實現的; Java虛擬機的實現可以自由地以原子方式或兩部分寫入長和雙值。

鼓勵實施Java虛擬機,以避免在可能的情況下分割64位值。鼓勵程序員將共享64位值聲明爲volatile,或正確同步其程序以避免可能的複雜情況。

編輯:雖然頂部聲明仍然有效,因爲它是 - 線程安全,有必要增加volatile,以反映不同Threads的即時更新,以反映參考更新。 Thread的行爲是使其本地副本,而volatile它將執行happens-before relationship換句話說Threads將具有相同的Map狀態。

+0

但是線程的安全性不僅僅是原子性 - 更新應該及時地被其他線程看到,對吧?在這方面不存在不穩定的幫助? – GurV

+0

@GurwinderSingh你是對的。我更多地閱讀了內存模型,而引用更新是原子的 - 它不是線程安全的,因爲另一個'Thread'可能已經有了它的舊版本,而新版本正在構建。隨着波動,有發生之前的關係,這阻止了這一點。 –

2

如果一個類在多個線程同時使用時做了正確的事情,那麼這個類就是「線程安全的」。除非您可以說出「正確的東西」的含義,特別是「多線程使用正確的東西」意味着什麼,否則無法確定類是否是線程安全的。

如果線程A調用foo.isPresent("X")並返回true,然後線程B調用foo.refresh(),然後線程A調用foo.getName("X"),什麼是正確的?

如果你要聲明「線程安全」,那麼你必須非常明確的調用者應該期望在這種情況下。