Java同時、volatile+可変コンテナオブジェクトはスレッドの安全を保証することができますか?!


『Java同時プログラミング実戦』第3章原文
「Java同時プログラミング実戦」の3.4.2例:Volatileタイプを使用して可変オブジェクトをパブリッシュする
前述のUnsafeCachingFactorizerクラスでは、最新の数値とその因数分解結果を2つのAtomicReferences変数で保存しようとしたが、この方法はスレッドが安全ではない.この2つの関連する値を原子的に同時に読み取りまたは更新できないためだ.同様に、volatileタイプの変数でこれらの値を保存してもスレッドは安全ではありません.しかしながら、場合によっては、可変オブジェクトは弱い形態の原子性を提供することができる.因数分解サーブレットは、キャッシュの結果を更新し、キャッシュ内の数値が要求された数値に等しいかどうかを判断することによって、キャッシュ内の因数分解結果を直接読み出すかどうかを決定する2つの原子操作を実行します.関連するデータのセットを原子的に実行する必要があるたびに、プログラムリスト3-12のOneValue Cacheなどの可変クラスを作成することを考慮することができます.
プログラムリスト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);  
   }  
} 

複数の関連変数にアクセスおよび更新する際に発生する競合条件の問題は、これらの変数をすべて1つの可変オブジェクトに保存することによって解消できます.可変オブジェクトの場合は、ロックを使用して原子性を確保する必要があります.可変オブジェクトの場合、スレッドがオブジェクトの参照を取得すると、別のスレッドがオブジェクトのステータスを変更する心配はありません.これらの変数を更新する場合は、新しいコンテナオブジェクトを作成できますが、既存のオブジェクトを使用する他のスレッドでは、オブジェクトが一貫した状態にあることがわかります.プログラムリスト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);  
   }  
} 

OneValueCacheは可変であり、対応するコードパスごとに1回しかアクセスできないため、cacheに関連する操作は干渉しません.複数の状態変数を含むコンテナオブジェクトを使用して不変条件を維持し、volatileタイプの参照を使用して可視性を確保することで、VolatileCachedFactorizerがロックを明示的に使用しない場合でもスレッドが安全になります.
ぶんせき
  • プログラムリスト3-13には『先チェック後実行』(Check-Then-Act)の競合条件が存在する.
  • OneValueCacheクラスの不変性は対象の原子性のみを保証する.
  • volatileは可視性のみを保証し、スレッドのセキュリティを保証できません.

  • 以上,オブジェクトの不変性+volatile可視性は,競合条件の同時問題を解決することはできないので,原文のこの結論は誤りである.
    更新
    疑惑は解決した.
    結論:cacheオブジェクトはservice()の中で1つの書き込み操作(新しいcacheオブジェクトを作成する)しかなく、残りは読み取り操作であり、ここでvolatileの適用シーンに合致し、cacheオブジェクトの他のスレッドに対する可視性を確保し、同時読み取りの問題が発生しない.また、返される結果はfactorsオブジェクトであり、factorsはローカル変数であり、cacheオブジェクトを逸脱させないため、ここでもスレッドは安全である.
    この問題はすでに質問エリアに提出され、知っています.https://segmentfault.com/q/10...https://www.zhihu.com/questio...