亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

Java 中,volatile+不可變容器對象能保證線程安全么?

Java 中,volatile+不可變容器對象能保證線程安全么?

慕無忌1623718 2019-02-20 02:51:32
《Java并發編程實戰》第3章原文 《Java并發編程實戰》中3.4.2 示例:使用Volatile類型來發布不可變對象 在前面的UnsafeCachingFactorizer類中,我們嘗試用兩個AtomicReferences變量來保存最新的數值及其因數分解結果,但這種方式并非是線程安全的,因為我們無法以原子方式來同時讀取或更新這兩個相關的值。同樣,用volatile類型的變量來保存這些值也不是線程安全的。然而,在某些情況下,不可變對象能提供一種弱形式的原子性。因式分解Servlet將執行兩個原子操作:更新緩存的結果,以及通過判斷緩存中的數值是否等于請求的數值來決定是否直接讀取緩存中的因數分解結果。每當需要對一組相關數據以原子方式執行某個操作時,就可以考慮創建一個不可變的類來包含這些數據,例如程序清單3-12中的OneValueCache。 程序清單 3-12 對數值及其因數分解結果進行緩存的不可變容器類 @Immutable class OneValueCache { private final BigInteger lastNumber; private final BigInteger[] lastFactors; public OneValueCache(BigInteger i, BigInteger[] factors) { lastNumber = i; lastFactors = Arrays.copyOf(factors, factors.length); } public BigInteger[] getFactors(BigInteger i) { if (lastNumber == null || !lastNumber.equals(i)) return null; else return Arrays.copyOf(lastFactors, lastFactors.length); } } 對于在訪問和更新多個相關變量時出現的競爭條件問題,可以通過將這些變量全部保存在一個不可變對象中來消除。如果是一個可變的對象,那么就必須使用鎖來確保原子性。如果是一個不可變對象,那么當線程獲得了對該對象的引用后,就不必擔心另一個線程會修改對象的狀態。如果要更新這些變量,那么可以創建一個新的容器對象,但其他使用原有對象的線程仍然會看到對象處于一致的狀態。程序清單3-13中的VolatileCachedFactorizer使用了OneValueCache來保存緩存的數值及其因數。當一個線程將volatile類型的cache設置為引用一個新的OneValueCache時,其他線程就會立即看到新緩存的數據。 程序清單 3-13 使用指向不可變容器對象的volatile類型引用以緩存最新的結果 @ThreadSafe public class VolatileCachedFactorizer implements Servlet { private volatile OneValueCache cache = new OneValueCache(null, null); public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = cache.getFactors(i); if (factors == null) { factorfactors = factor(i); cache = new OneValueCache(i, factors); } encodeIntoResponse(resp, factors); } } 與cache相關的操作不會相互干擾,因為OneValueCache是不可變的,并且在每條相應的代碼路徑中只會訪問它一次。通過使用包含多個狀態變量的容器對象來維持不變性條件,并使用一個volatile類型的引用來確??梢娦?,使得VolatileCachedFactorizer在沒有顯式地使用鎖的情況下仍然是線程安全的。 分析 程序清單3-13中存在『先檢查后執行』(Check-Then-Act)的競態條件。 OneValueCache類的不可變性僅保證了對象的原子性。 volatile僅保證可見性,無法保證線程安全性。 綜上,對象的不可變性+volatile可見性,并不能解決競態條件的并發問題,所以原文的這段結論是錯誤的。 更新 疑惑已經解決了。 結論:cache對象在service()中只有一處寫操作(創建新的cache對象),其余都是讀操作,這里符合volatile的應用場景,確保cache對象對其他線程的可見性,不會出現并發讀的問題。返回的結果是factors對象,factors是局部變量,并未使cache對象逸出,所以這里也是線程安全的。
查看完整描述

3 回答

?
慕容3067478

TA貢獻1773條經驗 獲得超3個贊

  • cache對象在service()中只有一處寫操作(創建新的cache對象),其余都是讀操作,這里符合volatile的應用場景,確保cache對象對其他線程的可見性,不會出現并發讀的問題。

  • 返回的結果是factors對象,factors是局部變量,并未使cache對象逸出,所以這里也是線程安全的。

查看完整回答
反對 回復 2019-03-01
?
慕容708150

TA貢獻1831條經驗 獲得超4個贊

一下個人理解:
(1).cache對象在service()中只有一處寫操作,但是多個線程都會執行這個寫操作。比如A線程帶入參數a1執行service方法之后,緩存里是number=a , lastFactors=[a];這時a2線程進入service方法帶入a,執行BigInteger[] factors = cache.getFactors(i); 取得了緩存數據[a]進行判斷時線程切換,來了個C線程帶入參數c執行完了方法。實際上此時的緩存是c和[c].理論上線程B讀的值已經是過期的了。。。只是因為“緩存”的業務意義使得這個過期值不會引起程序錯誤罷了。。。也就是說這個例子的線程安全體現在正好切合了這個業務。。。
(2).OneValueCache的不可變也是有限制的。BigInteger[]類型的數組接受外部參數后,用Arrays.copyOf能使得初始化后值不再變化。但是如果不是BigInteger類型而是其他類型,要保證該類型也是不可變對象才行。

總之我感覺,對于我這么菜的初學者來說,鎖還是最好用的。畢竟程序的優化是建立在沒有BUG的基礎上。萬一哪邊因為理解偏差導致隱含漏洞就跪了。

查看完整回答
反對 回復 2019-03-01
?
慕神8447489

TA貢獻1780條經驗 獲得超1個贊

我覺得這個問題,對于常量類來說,多線程讀的操作不存在線程安全問題,但是寫的操作要看具體應用場景對于線程安全的定義。比如說《Java并發編程實戰》那段,我覺得作者對于線程安全就是只看當前的cache,最后達成一個緩存的目的,至于這個緩存能不能立馬用到不在乎,反正肯定有線程會用到的。

查看完整回答
反對 回復 2019-03-01
  • 3 回答
  • 0 關注
  • 508 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號