フクロウの深夜翻訳:Volatileの原子性、可視性と秩序性


なぜvolatileを研究するために追加の文章を書くのですか?これは、合併の中で最も困惑し、最も誤解されている構造である可能性があるからです.私はvolatileを説明するブログをたくさん見たことがありますが、多くは不完全であるか、理解しにくいかのどちらかです.私は合併の中で最も重要な要素から始めます.
原子性原子性は不可分な操作である.それらはすべて実現するか、すべて実現しないか.Javaでの原子操作の最適な例は、変数に値を割り当てることです.
可視性とは、共有された変数に対するスレッドの変更や影響にかかわらず、他のスレッドを読むことが可視であることを意味します.
秩序性秩序性とは、ソースコード内の命令がコンパイラによって最適化のために実行順序が変更されるかどうかを意味する.1つのスレッドの動作が別のスレッドに対して乱れている可能性があります.
これらの要因を理解するために例を挙げます.
public class MyApp
{
    private int count = 0;
    public void upateVisitors() 
    {
       ++count; //increment the visitors count
    }
}

Hint: read-modify-write
このコードには、アプリケーション(Webページ)の訪問者数を更新しようとする方法があります.このコードの問題は、++count命令が原子的ではなく、3つの独立した命令を含むことです.
temp = count;   (read)
temp = temp + 1;   (modify)
count = temp;  (write)

したがって、あるスレッドがこの操作を実行している場合、この命令は別のスレッドによってプリ占有されてもよい.原子的な操作ではありませんcountの値が10で、次の実行順序があるとします.
2つのスレッドが値(10)を同時に読み出し、互いに値を1つ加算するという不一致な時点で発見されます.そのため、このプロセスでは増加した操作が失われました.実際の出力がスレッドインタリーブの結果に依存する場合,この場合を競合条件(race condition)と呼ぶ.ここでは一度紛失して増加した.では、合併のどのような面がここで欠けていますか?原子性単一のインスタンスを作成する例をもう1つ考えます(もちろん悪い例です):
public Singleton getInstance()
{
   if(_instance == null)
   { 
      _instance = new Singleton();
   }
}

Hint: check-then-act
もう一度、両方のスレッドがこのインスタンスをnullと判断し、ifコードブロックに入った可能性がある.これにより、2つのインスタンスが作成されます.ここでの問題は,コードブロックが原子的ではなく,インスタンスの変化が他のスレッドに見えないことである.このような複数のスレッドで同時に実行できない部分をキーセクション(critical section)と呼ぶ.重要な部分ではsynchronizedブロックとsynchronizedメソッドを使用する必要があります.
やはり原子性は原子性を確保するために、私たちは通常ロックを使って反発を確保します.次の例を参照すると、1つの銀行口座はsynchronized方法で鍵をかけます.
class BankAccount {
 private int accountBalance;
 synchronized int getAccountBalance() {
    return accountBalance;  
 }
 synchronized void setAccountBalance(int b) throws IllegalStateException {
    accountBalance = b;
    if (accountBalance < 0) {
     throw new IllegalStateException("Sorry but account has negative Balance");
    }
 }
 void depositMoney(int amount) {
    int balance = getAccountBalance();
    setAccountBalance(balance + amount);
 }
 void withdrawMoney(int amount) {
    int balance = getAccountBalance();
    setAccountBalance(balance - amount);
 }
}

共有変数balanceへのアクセスは、ロックによって保護され、データ競合に問題はありません.このクラスに問題がありますか?あります.1つのスレッドがdepositMoney(50)を呼び出し、別のスレッドがwithdrawMoney(50)を呼び出し、balanceの初期値が100であると仮定する.理想的には、操作完了後のbalanceは0であるべきである.しかし、この結果を保証することはできません.
  • depositMoney操作読取のbalance値は100
  • である.
  • withdrawMoney操作読み出しのbalance値も100であり、これに基づいて50元を減算して50元とする.
  • 最終depositMoneyは、前に見たbalanceの値に50を加えて150とする.

  • 再び原子性が保証されていないため、更新が失われた.両方の方法が同期として宣言されると、方法全体でロックが確保され、変更は原子的に行われます.
    可視性については、1つのスレッドの操作が別のスレッドに表示されている場合、他のスレッドもそのすべての操作の結果を観察します.次の例を考えます.
    public class LooperThread extends Thread
    {
        private boolean isDone = false;
        public void run() 
        {
           while( !isDone ) {
              doSomeWork();
           }
        }
        public void stopWork() {
           isDone = true;
        }
    }

    ここに何が欠けていますか?LooperThreadのインスタンスが実行中であると仮定し、プライマリ・スレッドはstopWordを呼び出してそれを中止する.この2つのスレッド間では同期は実現されていません.コンパイラは、最初のスレッドでisDoneに対して書き込み操作が行われていないと判断し、isDoneを1回だけ読み込むことを決定する.すると、スレッドが爆発!一部のJVMはこのようにして無限ループにする可能性があります.そのため、答えは明らかに可視性に欠けている.
    秩序性の秩序性について話すのは、事の発生の順序である.次の例を考えます.
    上記の場合、スレッド2はvalue = 0を印刷できますか?実は可能です.コンパイラ再ソートでは、result=truevalue=1より前に表示される可能性があります.value = 1は、スレッド2を表示しない場合もあり、その後、スレッド2はvalue = 0をロードする.volatileを使ってこの問題を解決できますか?
    CPUアーキテクチャ(多層RAM)CPUは、通常マルチコア化され、スレッドは異なるコア上で実行されます.また、次の図に示すように、異なるレベルのキャッシュもあります.
    volatile変数が任意のスレッドによって特定のコアに書き込まれると、各コアには変数の古い値があるため、他のすべてのコアの値が更新される必要があります.メッセージはすべてのカーネルに渡され、値が更新されます.
    volatile Javaドキュメントによると、変数がvolatileとして宣言されている場合、Javaメモリモデル(JDK 5以降)は、すべてのスレッドに変数の一致値が表示されることを保証します.volatileはsynchronizedの親戚のようなもので、volatileデータを読み取るのはsynchronizedブロックに入るようなもので、volatileデータを書き込むのはsynchronizedブロックから離れるようなものです.volatile値が書き込まれると、この値はローカルプロセッサのキャッシュではなくプライマリ・メモリに直接書き込まれ、メッセージを送信することによって他のカーネルのキャッシュ値の更新を通知します.Volatileは原子的操作ではありません
    volatileは順序性と可視性を保証するが、反発または原子性を保証しない.ロックは原子性,可視性,順序性を保証する.したがってvolatileはsynchronizedに代わることはできません.
    volatile読み取りと書き込みvolatileは、コンパイラが生成した命令が実際のソースコード命令で定義された順序以外の順序で操作結果を実行できないことを意味する順序保証を提供する.生成された命令の順序は、ソースコードの元の順序とは異なる場合がありますが、生成された効果は同じでなければなりません.Java Docでは、以下の読み書きについても観察する必要があります.
    スレッドがvolatile変数を読み出すと、volatileの最新の変化だけでなく、変化をもたらすコードの副作用も表示されます.
    volatileの読み書きについては、次の内容を理解する必要があります.
  • あるスレッドがvolatile変数に書き込まれ、別のスレッドが書き込みを見ると、最初のスレッドはvolatile変数に書き込むまでメモリの変化に関する2番目のスレッドに伝える.
  • ここで、スレッド2はスレッド1の内容を見た.

  • finalタイプのvolatile変数を宣言できますか?変数がfinalの場合、その値を変更することはできません.volatileは、他のスレッドに表示される共有変数の変更を確認します.これは許可されず、コンパイルエラーが発生します.
    なぜ私たちは同時プログラミングでlong/doubleをvolatileと宣言しますか?デフォルトではlong/doubleの読み書きは原子的ではありません.非原子的なdouble/long書き込みは、上位32ビットと下位32ビットの2つの書き込みとして扱われます.これにより、あるスレッドは別のスレッドが書き込まれた64ビット値の上位32ビットを見、2番目のスレッドは別のスレッドから書き込まれた下位32ビットを見ることができます.volatileのlong/doubleタイプ変数は常に原子的である.
    Volatile vs Atomicクラス
    public class MyApp
    {
        private volatile int count = 0;
        public void upateVisitors() 
        {
           ++count; //increment the visitors count
        }
    }

    もしcountをatomicと宣言したら、このコードは正常に実行できますか?可能であり、変数を増加または減少させる場合には、atomicクラスを使用することが望ましい.AtomicIntegerは、通常、volatileまたはCASを使用してスレッドセキュリティを実現する.
    より多くの開発技術、面接チュートリアル、インターネット会社のプッシュを知りたいなら、私の微信の公衆番号に注目してください.不定期で福祉が支給されますよ~