2010-02-03 77 views
9

閱讀本DZone article關於Java併發我在想,如果下面的代碼:Java中的volatile變量問題


    private volatile List list; 
    private final Lock lock = new ReentrantLock(); 

    public void update(List newList) { 
     ImmutableList l = new ImmutableList().addAll(newList); 
     lock.lock(); 
     list = l; 
     lock.unlock(); 
    } 

    public List get() { 
     return list; 
    } 

等同於:


    private volatile List list; 

    public void update(List newList) { 
     ImmutableList l = new ImmutableList().addAll(newList); 
     list = l; 
    } 

    public List get() { 
     return list; 
    } 

在try {}最後{}塊被省略簡潔。我假設ImmutableList類是一個真正不可變的數據結構,它擁有自己的數據,例如google-collections庫中提供的數據。由於list變量是不穩定的,基本上發生的是一個即時拷貝,跳過使用鎖定不是安全的嗎?

+0

我認爲兩者都應該表現得完全一樣,但它取決於'ImmutableList'的實施。 我期望在'ImmutableList'上調用'addAll()'會拋出一個Exception,所以鎖永遠不會被使用。 – 2010-02-03 21:43:07

回答

6

在這個非常具體的例子中,我認爲你可以沒有鎖定變量重新分配。

一般來說,我認爲使用AtomicReference代替volatile變量會更好,因爲內存一致性效果相同,意圖更清晰。

0

我認爲volatile的默認同步行爲不能保證ReentrantLock行爲,所以它可能有助於提高性能。否則,我認爲這很好。

1

重新閱讀後,它們是等效的。

+0

在將引用鎖定到列表之前,另一個線程複製newList時出現了什麼問題? – Kevin 2010-02-03 18:04:12

+0

@Clint跟在凱文的問題上,因爲l是方法的本地,並且列表更新被保護,調用get()的線程是否仍然會得到一致的結果? – teto 2010-02-03 18:12:31

+0

@是的。成員變量列表未被修改,只有參考正在更新。 – Kevin 2010-02-03 18:18:10

4

是的,這兩個代碼示例在併發環境中的行爲方式相同。易失性字段爲,因此在一個線程調用update()(它將用新列表替換列表)後,所有其他線程的get()將返回新列表。

但是,如果你有使用它這樣的代碼:

list = get() 
list = list.add(something) // returns a new immutable list with the new content 
update(list) 

然後在任的這些代碼示例(如預期它不會工作,如果兩個線程做並行,然後所做的更改其中一個可能被另一個覆蓋)。但是,如果只有一個線程正在更新列表,或者新值不依賴於舊值,那麼沒有問題。

+0

get()返回一個ImmutableList,正如我在問題描述中所述,試圖修改它只會引發異常。 – teto 2010-02-03 18:35:13

+0

總是有可能創建一個新的不變列表,其中包含舊的不可變列表的元素以及更多內容 - 請參閱我的註釋add()方法如何返回新列表。這就是它在函數式編程中的做法。 (如果您的示例使用google集合的ImmutableList,那麼它可能沒有這樣的操作,但是每個函數式編程語言的庫都有。) – 2010-02-03 20:58:12

+0

原文沒有[現在]更新列表。這就是爲什麼同步收藏很少能達到你想要的效果的一部分原因。 /文章中的示例非常簡單,可以輕鬆地由cas循環替換。 – 2010-02-04 06:11:17

1

如果我們正在討論時間和內存的可見性。易失性讀取非常接近正常讀取所需的時間。所以如果你在做get()很多,那麼幾乎沒有什麼區別。執行易失性寫入所需的時間約爲獲取和釋放鎖定的1/3。所以你的第二個建議要快一點。

存儲可見性,因爲大多數人認爲是等價的,即任何讀類似於任何讀鎖定收購之後的任何寫操作之前鎖定收購發生之前任何後續的寫

1

以下標準之前揮發性讀發生之前必須滿足volatile變量以提供期望的線程安全性:

  1. 寫入變量不取決於其當前值。
  2. 該變量不參與其他變量的不變量。

,因爲二者都在這裏會見了 - 代碼是線程安全