Javaマルチスレッド(3)Volatileの実現原理

5063 ワード

Volatile変数
プログラム設計では、特にC言語、C++、C#、Java言語でvolatileキーワードを使用して宣言される変数またはオブジェクトは、通常、マルチスレッドに関連する特殊な属性を有し、最適化および(または)します.通常、volatileキーワードは、コンパイラが「コード自体」によって変更できないと考えているコード(変数/オブジェクト)を最適化することを阻止するために使用されます.C言語ではvolatileキーワードを使用して、コンパイラが後で定義した変数がいつでも変更される可能性があることを注意することができます.したがって、コンパイルされたプログラムは、この変数を格納または読み出すたびに、変数アドレスから直接データを読み出す必要があります.volatileキーワードがない場合、コンパイラは読み取りと記憶を最適化し、レジスタの値を一時的に使用する可能性があります.この変数が別のプログラムで更新されると、一致しない現象が発生します.C環境ではvolatileキーワードの真の定義と適用範囲がしばしば誤解される.C++、C#、JavaはCから神秘的にvolatileを「継承」しているが、これらのプログラミング言語ではvolatileの使い方と意味は大きく異なる.【ウィキペディア】
 
コンパイラは、すばやく読み書きするためにレジスタキャッシュにデータを格納し、volatile修飾を使用した後、この変数を最適化しないで、読み出すたびにメモリから読み取るようにコンパイラに伝えるという意味です.
 
ロックには、反発(mutual exclusion)と可視性(visibility)の2つの主要な特性があります.反発とは、1回に1つのスレッドが特定のロックを持つことしか許されないため、この特性を使用して共有データに対する協調アクセスプロトコルを実現することができ、これにより、共有データを一度に1つのスレッドしか使用できない.可視性をさらに複雑にするには、ロックを解除する前に共有データに対する変更が、その後ロックを取得する別のスレッドに対して可視であることを確認する必要があります.同期メカニズムによって提供される可視性保証がない場合、スレッドに表示される共有変数は、変更前の値または一致しない値であり、多くの深刻な問題を引き起こす可能性があります.
 
Volatile変数はsynchronizedの可視性特性を持つが,原子特性は備えていない.すなわち、スレッドはvolatile変数の最新値を自動的に発見することができる.Volatile変数はスレッドのセキュリティを提供するために使用できますが、複数の変数間または変数の現在の値と修正後の値との間に制約がない非常に限られた使用例のセットにのみ適用できます.したがって、volatileを単独で使用すると、カウンタ、反発ロック、または複数の変数に関連する不変式を有するクラスを実現するのに十分ではありません.
 
 
1.1 volatile変数を正しく使用する条件
ロックの代わりにvolatile変数を使用するのは、限られた場合に限られます.volatile変数を理想的なスレッドセキュリティにするには、次の2つの条件を同時に満たす必要があります.
  • 変数に対する書き込み操作は、現在の値に依存しません.
  • この変数は、他の変数を有する不変式に含まれていない.

  • 最初の条件の制限によりvolatile変数はスレッドセキュリティカウンタとして使用できません.インクリメンタルオペレーション(x++)は単独オペレーションと同様に見えるが、実際には読み取り-修正-書き込みオペレーションシーケンスからなる組合せオペレーションであり、原子的に実行されなければならないが、volatileは必要な原子特性を提供できない.正しい動作を実現するには、xの値を動作中に一定に保つ必要がありますが、volatile変数では実現できません.(ただし、値を単一のスレッドからのみ書き込むように調整すると、最初の条件は無視できます.)
       1.            
    @NotThreadSafe 
    public class NumberRange {
        private int lower, upper;
    
        public int getLower() { return lower; }
        public int getUpper() { return upper; }
    
        public void setLower(int value) { 
            if (value > upper) 
                throw new IllegalArgumentException(...);
            lower = value;
        }
    
        public void setUpper(int value) { 
            if (value < lower) 
                throw new IllegalArgumentException(...);
            upper = value;
        }
    }
    

    この方法は範囲の状態変数を制限するので、lowerとupperフィールドをvolatileタイプとして定義すると、クラスのスレッドセキュリティを十分に実現できない.同期を使用する必要があります.そうでなければ、たまたま2つのスレッドが同じ時間に一致しない値を使用してsetLowersetUpperを実行すると、範囲が一致しない状態になります.例えば、初期状態が(0, 5)であり、同じ時間内にスレッドAがsetLower(4)を呼び出し、スレッドBがsetUpper(3)を呼び出す場合、この2つの操作が交差して格納された値が条件に合致しないことは明らかであり、2つのスレッドは、最も後の範囲値が(4, 3)であり、無効な値であるように、不変性を保護するための検査によって行われる.範囲の他の操作については、setLower()およびsetUpper()を原子化する必要があります.フィールドをvolatileタイプとして定義することは、この目的を達成できません.  
       
    1.2性能の考慮
    「Xは常にYより速い」など、正確で包括的な評価は難しい.特にJVMの内在的な操作については.(例えば、VMがロック機構を完全に削除できる場合があり、volatileおよびsynchronizedのオーバーヘッドを抽象的に比較することは困難である.)すなわち、現在のほとんどのプロセッサアーキテクチャでは、volatile読み取り操作のオーバーヘッドは非常に低く、非volatile読み取り操作とほぼ同じである.一方、volatile書き込み操作のオーバーヘッドは、非volatile書き込み操作よりも多く、可視性を保証するにはメモリ定義(Memory Fence)が必要であるため、それでもvolatileの総オーバーヘッドはロック取得よりも低い.
     
    多くの同時性専門家は、ロックを使用するよりもエラーが発生しやすいため、volatile変数からユーザーを遠ざけることが多い.しかしながら、いくつかの良好に定義されたモードに慎重に従うと、volatile変数を多くの場合に安全に使用することができる.volatileの使用の制限を常に覚えておく必要があります.volatileは、ステータスがプログラム内の他のコンテンツとは独立している場合にのみ使用できます.このルールでは、これらのモードを不安全な使用例に拡張することは避けられます.
     
    1.3 volatileのモードを正しく使用する
       2.   volatile           
    volatile boolean shutdownRequested;
    
    ...
    
    public void shutdown() { shutdownRequested = true; }
    
    public void doWork() { 
        while (!shutdownRequested) { 
            // do stuff
        }
    }
    

      shutdown()メソッド、すなわち別のスレッドからループ外部から呼び出される可能性が高いため、shutdownRequested変数の可視性が正しく実現されるように、何らかの同期を実行する必要がある.(JMXリスナー、GUIイベントスレッドのオペレーションリスナー、RMI経由、Webサービス経由などから呼び出される場合があります).しかしながら、synchronizedブロックを用いた記述サイクルは、リスト2に示すvolatile状態フラグを用いた記述よりもはるかに面倒である.volatileは符号化を簡略化し、ステータスフラグはプログラム内の他のステータスに依存しないため、volatileを使用するのに適している.
    このタイプのステータスタグの共通の特性は、通常1つのステータス変換のみである.shutdownRequestedフラグがfalseからtrueに変換され、プログラムが停止する.このモードは、遷移が到来する状態フラグを拡張することができるが、遷移周期が気づかれない場合にのみ拡張することができる(falseからtrue、さらにfalseに変換する).さらに,原子変数などのいくつかの原子状態変換機構も必要である.
     
     
    ロックと比較してVolatile変数は非常に単純であるが同時に非常に脆弱な同期機構であり、場合によってはロックよりも優れた性能と伸縮性を提供する.volatileの使用条件、すなわち変数が本当に他の変数と自分の以前の値とは独立している場合、volatileの代わりにsynchronizedを使用してコードを簡略化することができる場合がある.しかしながら、volatileを使用するコードは、ロックを使用するコードよりもエラーが発生しやすいことが多い.本明細書で説明するパターンは、volatileの代わりにsynchronizedを使用することができる最も一般的ないくつかの例をカバーする.これらのモードに従うと(それぞれの制限を超えないように注意)、volatile変数を使用してパフォーマンスを向上させるために、ほとんどの使用例を安全に実装できます.