JavaにおけるVolatileの下位原理と応用

6303 ワード

Volatile定義と原理
Java              ,                  ,                 

Java言語は、マルチプロセッシング開発において、共有変数の「可視性」を確保するためにViolatileを提供します.すなわち、別のスレッドが共有変数を変更すると、別のスレッドがこの変更の値を読み取ることができます.これは軽量級synchronizedであり、スレッドコンテキストの切り替えやスケジューリングを引き起こすことなく、実行コストが小さくなります.
Violatile修飾変数を使用すると、アセンブリフェーズでlock接頭辞命令が1つ多くなり、マルチコアプロセッサの次回に2つのことが起こります.
  • 現在のプロセッサキャッシュ行のデータをシステムメモリ
  • に書き込む.
  • このメモリを書き戻す操作は、他のCPUでメモリアドレスをキャッシュしたデータを無効にします.

  • 通常、プロセッサとメモリの間には処理速度を向上させるためにいくつかのレベルのキャッシュがあり、プロセッサはメモリ内のデータを内部キャッシュに読み込んでから操作するが、メモリをキャッシュするタイミングが分からないため、1つのプロセッサで変更した変数値は、必ずしもキャッシュできるとは限らず、このような変数変更は他のプロセッサに「見えない」ようになった.ただし、Volatileで修飾された変数は、書き込み操作の際に、この変数が存在するキャッシュ行のデータを強制的にメモリに書き込み戻しますが、メモリに書き込み戻しても、他のプロセッサが内部のキャッシュデータを使用する可能性があり、変数が一致しないため、マルチプロセッサでは、各プロセッサのキャッシュが一致することを保証するため、キャッシュコンシステンシプロトコルが実装され、各プロセッサはバスに伝播したデータを嗅ぐことで、自分のキャッシュの値が期限切れであるかどうかを確認し、期限切れになるとキャッシュ行が無効に設定され、次回使用するとメモリから読み直されます.
    追加バイト最適化Volatileパフォーマンス
          ,          64           。
    

    JDK 7の並発注には、volatile変数を使用する場合に、キューリストキューとエンキューのパフォーマンスを最適化するためにバイトを追加するキュー集合クラスLinkedTransferQueueがあります.キューには2つの共有ノード、ヘッダノード、テールノードが定義されており、volatileを使用した内部クラス定義によって、2つの共有ノードのバイト数を64バイトに増やすことで効率を最適化し、具体的には以下のように分析されています.
      CPU L1、L2 L3        64   ,          
    

    つまり、キューのヘッダ・ノードとエンド・ノードが64バイト未満の場合、プロセッサは同じキャッシュ・ラインに読み、マルチプロセッサの下で各プロセッサが同じヘッダ・エンド・ノードをキャッシュし、1つのプロセッサがヘッダ・ノードを変更しようとすると、バッファ・ロー全体がロックされ、キャッシュの一貫性のメカニズムの下で、他のプロセッサは、自分のキャッシュ内のエンドノードにアクセスできません.エンドノードはキュー内で頻繁にアクセスするため、パフォーマンスに影響します.一方、バイトを入力することで、変更時にヘッダがロックされないように、ヘッダ・ノードを異なるキャッシュ・ラインにロードします.ただし、次の2つのシーンでは、この最適化方法を使用するべきではありません.
  • キャッシュ行64バイト幅以外のプロセッサ(補足バイト長を自分で調整する原理は同じ)
  • 共有変数は頻繁に書かれません.バイトを追加するとCPUの読み取り性能が低下し、共有変数の書き込み頻度が低いとロックされる確率も低いため、
  • 同士のロックを避ける必要はない.
    Volatileは原子性を保証できない
    volatile   “     ”,         ,          。 
    

    次の例のように
    public class Test {
        public volatile int inc = 0;
    
        public void increase() {
            inc++;
        }
    
        public static void main(String[] args) {
            final Test test = new Test();
            for(int i=0;i<10;i++){
                new Thread(){
                    public void run() {
                        for(int j=0;j<1000;j++)
                            test.increase();
                    };
                }.start();
            }
    
            while(Thread.activeCount()>1)  //           
                Thread.yield();
            System.out.println(test.inc);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

  • 上のプログラムの出力の結果はいくらですか?多くの人は10000だと思っているかもしれませんが、変数incを自増操作していると思っています.volatileは可視性を保証しているので、各スレッドでincを自増した後、他のスレッドでは修正後の値が見えますね.だから、10スレッドでそれぞれ1000回操作したので、最終的にincの値は1000*10=10000になるはずです.この中には間違いがあります.volatileキーワードは可視性を保証できますが、上のプログラムは原子性を保証できません.可視性は、毎回読み込まれる値が最新の値であることしか保証できませんが、volatileは変数の操作の原子性を保証できません.オートインクリメント操作は原子性を備えていないため、変数の元の値を読み出し、1加算操作を行い、ワークメモリに書き込むことを含む.すなわち、自増操作の3つのサブ操作が分割して実行される可能性があり、ある時点で変数incの値が10であれば、スレッド1が変数を自増操作し、スレッド1が変数incの元の値を先に読み出し、スレッド1がブロックされる可能性がある.そしてスレッド2は変数に対して自増操作を行い、スレッド2も変数incの元の値を読み取る.スレッド1は変数incに対してのみ読み出し操作を行い、変数に対して修正操作を行わないため、スレッド2のワークメモリにおけるキャッシュ変数incのキャッシュ行が無効になることはないので、スレッド2は直接incの値をホストして読み出し、incの値を発見した場合10、その後、プラス1操作を行い、11をワークメモリに書き込み、最後にメインメモリに書き込む.次にスレッド1は、1を加算する操作を行い、incの値が読み取られたため、スレッド1のワークメモリにおいてincの値は依然として10であることに注意して、スレッド1がincに1を加算する操作を行った後のincの値は11であり、その後、11をワークメモリに書き込み、最後にプライマリメモリに書き込む.では,2つのスレッドがそれぞれ1回の自己増加操作を行った後,incは1だけ増加した.
    ここまで説明すると、volatile変数を変更するときにキャッシュラインが無効になることを保証する変数があるのではないでしょうか.その後、他のスレッドが読みに行くと新しい値が読み出されますが、スレッド1が変数を読み取り操作した後、ブロックされるとinc値は変更されませんので注意してください.そしてvolatileは、変数incの値に対するスレッド2の読み出しがメモリから読み出されることを保証するが、スレッド1は変更されていないため、スレッド2は変更された値をまったく見ない.
    根源はここにあり,自己増加操作は原子的操作ではなくvolatileも変数に対するいかなる操作も原子的であることを保証できない.したがって、Violatileを使用して変数を修飾する場合は、プログラム内の状態変数などの変数の書き込み操作が原子的であることを保証し、その変数の変更は現在の値に依存しない.
    リファレンス
    Java同時プログラミング:volatileキーワード解析