Javaキーワードvolatileの理解と正確な使用

3305 ワード

概要
Java言語におけるキーワードvolatileは軽量級のsynchronizedと呼ばれています。synchronizedに比べて、volatile符号は比較的簡単で実行時のオーバヘッドが少ないですが、正確で合理的にvolatileを適用できるのはそんなに簡単ではありません。鍵を使うよりも間違いやすいので、ここでは主にvolatileの使用基準を紹介します。及び使用中の注意点。
なぜvolatileを使うのですか?
(1)簡易性:同期が必要な場面では、volatile変数を使うのはロックを使うよりも簡単です。
(2)性能:場合によってはvolatile同期機構を使用する性能はロックより優れている
(3)volatileの操作は、ロックのようにブロックされにくいです。
volatile特性
(1)volatile変数は、synchronizedの視認性を持っています。また、一つのフィールドがvolatileとして宣言されている場合、javaスレッドメモリモデルは、すべてのスレッドがこの変数の値を見ていることを保証します。
(2)命令並び替え禁止
(3)原子性が保証されていない
注:
①並べ替え:並べ替えは、通常コンパイラや運転中の環境がプログラムの性能を最適化するために行う命令の並べ替え実行の手段です。
②原子性:中断できない1つまたは複数の動作
③視認性:ロックは2つの主要な特性を提供しています。相互反発と視認性、つまり1回に1つのスレッドがある特定のロックを持つことができるので、この特性を利用してデータを共有するための協調アクセスプロトコルを実現することができます。このように、1回に1つのスレッドだけが共有データを使用することができます。視認性はより複雑であり、ロックを解除する前に共有データに対して変更が行われることを確認しなければならない。その後、ロックを取得する他のスレッドが見られる。
volatileの実現原理
volatileを宣言した変数を書き込み操作すると、JVMはこの変数がキャッシュされている行のデータをシステムメモリに返信し、キャッシュ整合性プロトコルに従って、各プロセッサはバスで送信されたデータを嗅ぎ付けることによって、自分のキャッシュの値が期限切れているかどうかを確認するロックプレフィックスのコマンドをプロセッサに送信します。プロセッサが自分のキャッシュ行に対応するアドレスが変更されていることを発見すると、現在のプロセッサのキャッシュ行を無効状態に設定し、次回同じメモリアドレスにアクセスすると、キャッシュ行のパディングを強制的に実行します。
volatileを正しく使うシーン
volatileは主にマルチスレッド環境におけるメモリの見えない問題を解決するために用いられる。書き込みが多いと変数の同期問題が解決されますが、多く書くとスレッドの安全問題が解決されません。例えば:
1、volatileを使うシーンには不向き(非原子的操作)
(1)反例

​private static volatile int nextSerialNum = 0;
public static long generateSerialNum() {
  return nextSerialNum++;
}
この方法の目的は呼出毎に異なる自己増値を返すことを保証することであるが、結果は理想的ではなく、インクリメンタルオペレータ(+)は原子操作ではなく、実際には読み取り−修正−書込みオペレーティングシーケンスからなる組み合わせ操作であり、第二のスレッドが第一のスレッドで古い値を読み取り、新しい値を書き込む間にこの領域を読み込む場合、第二のスレッドは、最初のスレッドと同じ値に読み込まれます。
(2)正例
実は上記の逆例のシーンに対して、JDK 1.5 java.util.co ncurrent.atomicで提供された原子包装の種類を使用して、原子性操作を保証することができます。

private static AtomicInteger nextSerialNum = new AtomicInteger(0);
public static long generateSerialNum() {
  return nextSerialNum.getAndIncrement();
}
2、volatileを使うにふさわしいシーン
日常の仕事の中でvolatileは状態マークのシーンに多く見られます。
スレッドを通して他のスレッドのシーンを終了します。
(1)反例

private static boolean stopThread;
public static void main(String[] args) throws InterruptedException {
  Thread th = new Thread(new Runnable() {
   @Override
   public void run() {
     int i = 0;
     while (!stopThread) {
      i++;
     }
   }
  });
  th.start();
  TimeUnit.SECONDS.sleep(2);
  stopThread = true;
}
実行後、プログラムはまったくループを終了できないことが分かりました。Java言語規範は他のスレッドに対する書き込みの値が保証されていないため、共有変数stopThread状態をメインスレッドmain関数が修正しても、thスレッドが見えなくなり、最終的にループが終了できなくなりました。
(2)正例

private static volatile boolean stopThread;
public static void main(String[] args) throws InterruptedException {
  Thread th = new Thread(new Runnable() {
   @Override
   public void run() {
     int i = 0;
     while (!stopThread) {
      i++;
     }
   }
  });
  th.start();
  TimeUnit.SECONDS.sleep(2);
  stopThread = true;
}
共有変数stopThreadをキーワードvolatileで修飾することで、主スレッドmain関数が共有変数stopThread状態を修正した後にスレッドthに対してすぐに見えるので、2秒以内にスレッドthが停止します。
締め括りをつける
本稿では、volatileの特性紹介とvolatileの実現原理を通して、最後にvolatileの特性を組み合わせて例を挙げて、それを使用する過程で注意すべき使用規則を説明します。