2013-05-03 20 views
8

我想寫一個簡單的線程安全的類,可以用來設置或獲取一個整數值。如何使用volatile變量編寫簡單的線程安全類?

最簡單的方法是使用同步關鍵字:

public class MyIntegerHolder { 

    private Integer value; 

    synchronized public Integer getValue() { 
     return value; 
    } 

    synchronized public void setValue(Integer value) { 
     this.value = value; 
    } 

} 

我也可以嘗試使用揮發性

public class MyIntegerHolder { 

    private volatile Integer value; 

    public Integer getValue() { 
     return value; 
    } 

    public void setValue(Integer value) { 
     this.value = value; 
    } 

} 

揮發性關鍵字線程類 - 安全

考慮事件的序列如下:

  1. 線程A將值設置爲5。
  2. 線程B將值設置爲7。
  3. 線程C-讀取值。

它從Java語言規範遵循

  • 「1」 之前發生
「3」
  • 「2」 之前發生 「3」

    ,但我不明白它是如何遵循規範「1」發生在「2」之前,所以我懷疑「1」不是發生在「2」之前。

    我懷疑線程C可以讀7或5。我覺得跟揮發性關鍵字類不是線程安全和下面的順序也是可能的:

    1. 線程A設置值至5。
    2. 線程B將值設置爲7。
    3. 線程C-讀取7.
    4. 螺紋d讀取5.
    5. 線程C-讀取7.
    6. 線d讀5
    7. ...

    我是在假設與揮發性是MyIntegerHolder糾正不線程安全

    是否有可能使用的AtomicInteger使線程安全的整數持有者:

    public class MyIntegerHolder { 
    
        private AtomicInteger atomicInteger = new AtomicInteger(); 
    
        public Integer getValue() { 
         return atomicInteger.get(); 
        } 
    
        public void setValue(Integer value) { 
         atomicInteger.set(value); 
        } 
    
    } 
    

    這裏是Java併發的片段在練習冊:

    讀取和原子變量具有相同的內存語義 作爲volatile變量的寫。」

    是什麼編寫線程安全的最佳(最好是非阻塞)方式 MyIntegerHolder?

    如果你知道答案,我想知道你爲什麼認爲它是正確的。它是否遵循規範?如果是這樣,怎麼樣?

  • +0

    我還在學習,如果有人能向我澄清一件事情,那將會很棒。 'synchronized'或'volatile'關鍵字如何使_thread safe_更安全?由於整數賦值和讀取本身是原子的,它會改變什麼嗎?這種方法不允許原子增量或無論如何獲取和設置。謝謝 – Sebi 2014-12-25 12:46:46

    +0

    @Sebi線程安全是一個運行在兩個或更多線程中的程序,沒有線程干擾另一個線程。你可以用不同的方式/技術來解決這個問題,一個線程干擾另一個線程。一個簡單的int x = x + x看起來是原子的,但不是(更多的是這個,搜索volatile和atomic)! 「讓它更安全」是解決問題的一種方法。這不會使線程更安全,只是解決相同問題的不同實現。 – 2015-04-02 04:02:50

    +0

    @ThufirHawat我知道什麼是線程安全的程序,或者在寫入之前需要多次讀取,但我確信有一些我不關於這個類。我無法掌握一個暴露getter和setter的整數類如何被用來做任何需要線程安全而不被外部鎖定的地方(「Integer」就足夠了)。 – Sebi 2015-04-03 17:26:40

    回答

    -1

    這個問題對我來說並不容易,因爲我認爲(不正確)知道關於的所有事情發生在關係之前,使人們對Java存儲器模型有一個完整的理解 - 以及易失性的語義。

    我發現這個文檔中的最好的解釋: "JSR-133: JavaTM Memory Model and Thread Specification"

    上述文件的最相關的片段是部分「7.3良構的處決」。

    Java內存模型保證程序的所有執行都是格式良好的。的執行是合式只有當它

    • 服從之前發生一致性
    • 服從同步順序的一致性
    • ...(一些其他的條件也必須是真實的)

    之前發生的一致性通常足以得出一個關於程序行爲的結論 - 但不是在這種情況下,由於揮發性寫不發生 - 在之前另一個易失性寫入。

    揮發性的MyIntegerHolder是線程安全,但它的安全性來自於同步順序的一致性

    在我看來,當線程B要將值設置爲7時,A並不告知B在它之前所做的所有事情(作爲其他答案中的一個建議) - 它僅向B通知有關值的易變變量。線程A會通知之B 一切(分配值與其它變量)如果線程B採取的行動被讀取,而不是寫(在這種情況下,就存在之前發生的這兩所採取的行動之間關係線程)。

    1

    如果您只需要獲取/設置一個變量,就足以像您那樣聲明變量。如果檢查如何的AtomicInteger設置/獲取工作,你會看到相同的實現

    private volatile int value; 
    ... 
    
    public final int get() { 
        return value; 
    } 
    
    public final void set(int newValue) { 
        value = newValue; 
    } 
    

    ,但你不能增加揮發性場原子這麼簡單。這是我們使用AtomicInteger.incrementAndGet或getAndIncrement方法的地方。

    4

    關鍵字​​是說如果Thread A and Thread B想訪問Integer,他們不能同時這樣做。 A告訴B等到我完成它。

    另一方面,volatile使線程更「友好」。他們開始相互交談,共同完成任務。所以當B試圖訪問時,A會告知B它所做的一切,直到那一刻。 B現在意識到這些變化,並且可以從A的左邊繼續工作。

    在Java中,你有Atomic出於這個原因,它下面使用關鍵字volatile,所以他們做的事情幾乎是一樣的,但它們可以節省您的時間和精力。

    你正在尋找的東西是AtomicInteger,你是對的。對於您正在嘗試執行的操作而言,這是最佳選擇。

    There are two main uses of `AtomicInteger`: 
    
    * As an atomic counter (incrementAndGet(), etc) that can be used by many threads concurrently 
    
    * As a primitive that supports compare-and-swap instruction (compareAndSet()) to implement non-blocking algorithms. 
    

    要回答的備忘

    這取決於你需要什麼你的問題。我不是說​​是錯誤的,volatile是好的,否則良好的Java人很久以前會刪除​​。沒有絕對的答案,有很多具體的案例和使用場景。

    我的一些書籤:

    Concurrency tips

    Core Java Concurrency

    Java concurrency

    更新

    從Java併發特異性ication提供here

    軟件包java.util.concurrent.atomic中

    ,支持在單個變量上無鎖線程安全 編程類的小工具包。

    Instances of classes `AtomicBoolean`, `AtomicInteger`, `AtomicLong`, and `AtomicReference` each provide access and updates to a single variable of the corresponding type. 
    Each class also provides appropriate utility methods for that type. 
    For example, classes `AtomicLong` and AtomicInteger provide atomic increment methods. 
    
    The memory effects for accesses and updates of atomics generally follow the rules for volatiles: 
    
    get has the memory effects of reading a volatile variable. 
    set has the memory effects of writing (assigning) a volatile variable. 
    

    Here

    Java編程語言volatile關鍵字:

    (在Java中的所有版本)有一個全球性的排序上的讀取和寫入volatile變量。這意味着訪問易失性字段的每個線程將在繼續之前讀取其當前值,而不是(可能)使用緩存值。 (但是,不能保證volatile讀寫的相對順序與常規的讀寫操作有關,這意味着它通常不是一個有用的線程結構。)

    +0

    我明白你的答案,但問題不是那麼簡單。我認爲具有volatile的MyIntegerHolder不是線程安全的,我提出了我的推理。在我看來,正確的答案必須參考規範和發生在關係之前的定義。也許我錯了,MyIntegerHolder是線程安全的 - 但在這種情況下,答案應該解釋什麼纔是理解發生之前關係的定義的正確方法。或者,也許問題不在於代碼(也許它是正確的),但是與規範(也許它不夠清楚)。 – 2013-05-03 12:39:56

    +0

    「A會告訴B所做的一切,直到那一刻」 - 你確定嗎?它是否遵循規範?在before-before關係的定義中,您可以看到寫入之前 - 在讀取之前,但我沒有看到片段說明寫入發生 - 在另一次寫入之前。 – 2013-05-03 12:42:07

    +0

    我知道AtomicInteger類似於volatile整數。但這個問題實際上是關於正確理解[Java語言規範](http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html# JLS-17.4.5) – 2013-05-03 12:49:22

    0

    Java語言規範的第17章定義了內存操作(如讀取和寫入共享變量)上的發生前關係。只有在寫入操作發生時,一個線程寫入的結果才能保證對另一個線程的讀取可見 - 在讀取操作之前。

    1. 同步的和揮發性的構建體,以及所述Thread.start()和的Thread.join()方法,就可以形成之前發生 關係。特別是:線程中的每個動作都會在該線程中的每個動作之前發生,該動作稍後按程序的順序進行。
    2. 一種監視器的開鎖(同步塊或方法出口)之前發生的每個後續鎖(同步塊或方法 條目)相同的監視器。並且因爲在關係 之前發生的事情是可傳遞的,所以在解鎖 之前發生的線程的所有動作都會發生 - 在監視之後的任何線程鎖定之後的所有動作之前。
    3. 到揮發性現場寫之前發生的是同一領域的每一個後續讀。寫入和讀取易失性字段與輸入和退出顯示器具有相似的內存一致性效果,但不要求 不需要互斥鎖定。
    4. 呼叫開始上線之前發生在啓動線程的任何行動。
    5. 線索中的全部動作發生,在任何其他線程成功從該線程聯接返回。

    參考:http://developer.android.com/reference/java/util/concurrent/package-summary.html

    從我的理解3手段:如果你寫的(不是基於讀取結果)/讀的罰款。如果你寫(根據讀取結果,例如增量)/讀取不好。由於揮發性「不意味着互斥鎖」

    0

    您與揮發性MyIntegerHolder是線程安全的。但是如果你正在做併發程序,AtomicInteger是首選,因爲它還提供了很多原子操作。

    考慮事件的序列如下:

    1. 線程A將值設置爲5。
    2. 線程B將值設置爲7。
    3. 線程C-讀取值。

    它從Java語言規範遵循

    • 「1」 之前發生 「3」
    • 「2」

    但我不之前發生 「3」看不出「1」 發生在「2」之前的規範後面,所以我懷疑「1」 在「2」之前沒有發生。

    我懷疑線程C可以讀7或5。我覺得跟 volatile關鍵字的類不是線程安全的

    你就在這裏「1」之前發生「3」和「2」發生在「3」之前。 「1」在「2」之前不會發生,但這並不意味着它不是線程安全的。問題是你提供的例子是不明確的。如果你說「將值設置爲5」,「將值設置爲7」,「讀取值」順序發生,你總是可以讀取7的值。把它們放在不同的線程中是無稽之談。但是如果你說3個線程沒有序列地同時執行,你甚至可以得到0的值,因爲「讀取值」可能首先發生。但是這對於線程安全來說沒有任何意義,從3個操作中沒有預期的順序。