簡體   English   中英

是否有必要使原始實例變量易變?

[英]Is it necessary to make a primitive instance variable volatile?

為了試驗多線程概念,我正在實現我自己的使用悲觀鎖定的AtomicInteger版本。 它看起來像這樣:

public class ThreadSafeInt {
    public int i; // Should this be volatile?

    public ThreadSafeInt(int i) {
        this.i = i;
    }

    public synchronized int get() {
        return i;
    }

    public synchronized int getAndIncrement() {
        return this.i++;
    }
    // other synchronized methods for incrementAndGet(), etc...
}

我編寫了一個測試,它接受一個ThreadSafeInt實例,將它提供給數百個線程,並使每個線程調用getAndIncrement 100,000次。 我所看到的是所有增量都正確發生,整數的值正好(number of threads) * (number of increments per thread) ,即使我沒有在原始實例變量i上使用volatile。 我期望如果我沒有使i易變,那么我會得到很多可見性問題,例如,線程1將i從0遞增到1,但是線程2仍然看到值0並且還將其遞增到1 ,導致最終值小於正確值。

我知道可見性問題是隨機發生的,並且可能取決於我的環境的屬性,因此即使可見性問題存在固有的潛在可能性,我的測試也可以正常工作。 所以我傾向於認為volatile關鍵字仍然是必要的。

但這是對的嗎? 或者我的代碼有一些屬性(可能是它只是一個原始變量等),我實際上可以信任它來消除對volatile關鍵字的需求?

即使我沒有在原始實例變量i上使用volatile。 我預計,如果我沒有讓我變得不穩定,那么我會遇到很多可見性問題

通過使getAndIncrement()get()方法synchronized ,正在修改i所有線程都正確地鎖定它以進行更新和值的檢索。 synchronized塊使得i不必變得volatile因為它們也確保了內存同步。

也就是說,您應該使用AtomicInteger來包裝volatile int字段。 AtomicInteger getAndIncrement()方法更新該值,而不必求助於synchronized塊,這仍然是線程安全的更快。

public final AtomicInteger i = new AtomicInteger();
...
// no need for synchronized here
public int get() {
    return i.get();
}
// nor here
public int getAndIncrement() {
    return i.getAndIncrement();
}

我會得到很多可見性問題,例如,線程1將i從0遞增到1,但是線程2仍然看到值0並且還將其遞增到1,導致最終值小於正確值。

如果你的get()方法沒有synchronized那么你的增量可能會被正確處理,但是其他線程看不到i正確發布的值。 但是這兩種方法都是synchronized這確保了讀寫時的內存同步。 synchronized也會執行鎖定,以便您可以執行i++ AtomicInteger再次更有效地處理內存同步和增量競爭條件。

更具體地說,當進入synchronized塊時,它與讀取存儲器屏障相交,該讀取存儲器屏障與從volatile字段讀取相同。 當退出synchronized塊時,它會越過寫入內存屏障,這與寫入volatile字段相同。 synchronized塊的不同之處在於還存在鎖定以確保一次只有一個人鎖定特定對象。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM