JAVAメモリモデルのキーワードvolatile

4792 ワード

JAVAメモリモデルのキーワードvolatile
volatileキーワードはJava仮想マシンが提供する最も軽量な同期メカニズムと言える.Javaメモリモデルはvolatileに対して特別なアクセスルールを定義している.変数がvolatileとして定義されると、可視性と禁止命令の再ソートの2つの特性が備えられます.
可視性
可視性とは、1つのスレッドがこの変数の値を変更したことを意味し、新しい値は他のスレッドに対してすぐに知ることができ、Java仮想マシン仕様の規定に従ってvolatile変数は依然としてワークメモリのコピーがあるが、特殊な操作順序性の規定のため、メインメモリに直接読み書きアクセスするように見える.volatile変数は、各スレッドにおいてコンシステンシの問題は存在しない(各スレッドのワークメモリにおいてvolatile変数が不一致であってもよいが、使用するたびにリフレッシュされるため、実行エンジンに不一致が見られないため、コンシステンシの問題は存在しないと考えられる).Javaでの演算は原子操作ではなく,volatile変数の演算は同時では安全ではない.以下のコードで示す
/**
volatile        
**/
public class VolatileTest{
public static volatile int rece=0;
public static void increase(){
race++;
}

private static final int THREADS_COUNT=20;

public static void main(String[] args){
Thread[] threads=new Thread[THREADS_COUNT];
for(int i=0;i1){
Thread.yield();
}
System.out.println(race);
}
}
}


このコードが正しく同時実行されれば、最後に出力される結果は200000であるはずですが、複数回実行した後、予想される結果は出ませんでした.問題は自己増加演算race++にあり、race++はrace=race+1に翻訳できる.raceを取り出すと、現在値が最新値であることが保証されますが、race+1という動作をすると、他の値がメインメモリのraceの値を変更した可能性があります.この場合、現在のスレッドraceの値は期限切れのデータなので、同期race+1の値は予想よりも小さいです.volatile変数は可視性しか保証できないため、以下の2つのルールの演算条件に合致しない場合は、ロック(synchronizedまたはjava.util.concurrentの原子クラス)によって原子性を保証する必要があります.
  • 演算結果は、現在の値に依存しないか、単一のスレッドのみが変数の値を変更できることを保証する.
  • 変数は、他の状態変数と共に不変制約に関与する必要はありません.

  • コマンド再ソート禁止
    ハードウェアアーキテクチャから言えば、命令並べ替えとは、cupが複数の命令をプログラムで規定された順序で各回路ユニットに別々に送信することを許可する処理を採用しているが、命令が任意に並べ替えられるわけではなく、cpuは命令依存状況を正しく処理してプログラムが正確な実行結果を得ることを保障する必要がある.通常の変数は、このメソッドの実行中に付与結果に依存するすべての場所で正しい結果が得られることを保証するだけで、変数付与操作の順序がプログラムコードの実行順序と同じであることを保証することはできません.命令の並べ替えがプログラムの同時実行にどのように干渉するかをコードで見ることができます.
    Map configOptions;
    char[] configText;
    volatile boolean initialized=false;
    configOptions=new HashMap();
    configText=readConfigFile(fileName);
    processConfigOptions(configText,configOptions);
    initialized=true;
    while(!initialized){
    sleep();
    }
    doSomethingWithConfig();
    

    上記の疑似コード記述のシーンは非常に一般的であるが、コンフィギュレーションファイルの処理中にコンカレントは発生しないのが一般的である.initialized変数を定義する際にvolatile変数で修飾しないと、命令再ソートの最適化により県城Aにある最後のコード「initialized=true」が早期に実行される可能性がある.これにより、スレッドBで構成情報のコードを使用するとエラーが発生する可能性があり、volatileキーワードはこのような状況を回避することができる.volatileシールド命令の並べ替えの意味はJdk 1.5で完全に修復され、これまでのjdkが直ちに変数をvolatileと宣言しても、並べ替えによる問題(主にvolatile変数の前後のコードに並べ替えの問題がある)を完全に回避することはできません.これもjdk 1.5以前のjavaではDCL(デュアルロック検出)が安全に使用できませんでした単一のパターンを実現する理由です.
    次のコードは、標準的なDCLの一例コードです.
    public class Singleton{
    private volatile static Singleton instance;
    public static Singleton getInstance(){
    if(instance==null){
    synchronized (Singleton.class){
    if(instance==null){
    instance=new Singleton();
    }
    }
    }
    return instance;
    }
    public static void main(String[] args){
    Singleton.getInstance();
    }
    }
    

    volatile修飾の変数があり、値付け後に「lock addl$0 X 0,(%esp)」操作を多く実行した(この操作はパッケージespレジスタの値に0を加えた空の操作であり、IA 32マニュアルではlock接頭辞がnop空の操作命令に合わせて使用できないことを規定している).この操作はメモリバリアに相当する(命令の並べ替え時に後の命令をメモリの並べ替え前の位置に並べ替えることはできない).1つのcpuのみがメモリにアクセスする場合、メモリバリアは必要ありませんが、複数のcpuが同じメモリに同時にアクセスし、そのうちの1つが別のメモリを観測している場合は、メモリバリアが一貫性を保つ必要があります.命令のlock接頭辞の役割は,本cpuのcacheをメモリに書き込むことであり,この書き込み動作は他のcupや他のカーネルにもそのcacheを無効化させる.この操作はcacheの変数に対してjavaメモリモデルのstoreとwrite操作を1回行ったことに相当するので、前のvolatile変数の変更を他のcpuにすぐに表示することができます.volatileの同期メカニズムの性能は、場合によってはロックよりも優れているが、仮想マシンによるロックの多くの除去と最適化により、volatileがsynchronizedよりもどれだけ速いかを量子化することは難しい.volatileが自分と比較した場合、volatile変数の読み取り操作の性能は通常の変数とほとんど変わらないが、書き込み操作はローカルコードに多くのメモリバリア命令を挿入してプロセッサが乱れずに実行されることを保証する必要があるため、遅い可能性がある.しかし、それでも多くのシーンでは、volatileの総オーバーヘッドはロックよりも低く、volatileとロックの中で選択した唯一の根拠は、volatileの意味がシーンの使用のニーズを満たすことができるかどうかだけです.
    Javaメモリモデルによるvolatile変数定義の特殊なルール
    Tが1つのスレッドを表し、VとWがそれぞれ2つのvolatile型変数を表すと仮定すると、read、load、use、assign、store、write操作を行う際に以下のルールを満たす必要がある.
  • スレッドTが変数Vに対して実行する前の動作がloadである場合にのみ、スレッドTは変数Vに対してuse動作を実行することができる.また、スレッドTが変数Vに対して実行する後の動作がuseである場合にのみ、スレッドTは変数Vに対してload動作を実行することができる.スレッドTの変数Vに対するuse動作は、スレッドTの変数Vに対するload,read動作と相互に関連するものと考えられ、連続して出現しなければならない(この規則は、ワークメモリにおいて、Vを使用するたびに、主メモリから最新の値をリフレッシュし、他のスレッドの変数Vに対する修正後の値が見えることを保証するために使用される)
  • .
  • スレッドTが変数Vに対して実行する前の動作がassignである場合にのみ、スレッドTは変数Vに対してstore動作を実行することができる.また、スレッドTが変数Vに対して実行する後の動作がstoreである場合にのみ、スレッドは変数Vに対してassign動作を実行することができる.スレッドTの変数Vに対するassign動作は、スレッドTの変数Vに対するstore、write動作と相互に関連するものと考えることができ、連続して出現しなければならない(この規則は、ワークメモリにおいて、Vを修正するたびに直ちに同期してホストメモリに格納し、他のスレッドが変数Vに対する修正を見ることができることを保証するために使用される)
  • .
  • は、動作AがスレッドTが変数Vに対して実施するuseまたはassign動作であると仮定し、動作Fが動作Aに関連付けられたloadまたはstore動作であると仮定し、動作Pが動作Fに応じた変数Vに対するreadおよびwrite動作であると仮定する.動作BはスレッドTがWに対して実施するuseまたはassign動作であると仮定し、動作Gは動作Bに関連付けられたloadおよびstore動作であると仮定し、動作Qは動作Gに対応するreadまたはwrite動作であると仮定する.AがBより先である場合、PはQより先である(この規則ではvolatile修飾を要求する変数は命令再ソート最適化されず、コードの実行順序がプログラムの順序と同じであることを保証する).