Javaメモリモデルとvolatileキーワード

6690 ワード

Javaメモリモデル(Java Memory Model)
Javaメモリモデル(JMM)は、Javaランタイムデータ領域とは異なり、プログラム内の各変数のアクセスルールを定義することが主な目的です.つまり、仮想マシンで変数をメモリに格納したり、メモリからデータを読み出したりするような最下位の詳細です.JMMは、すべての変数をプライマリメモリに格納することを規定しているが、各スレッドには独自のワークメモリがあり、スレッドのワークメモリには、そのスレッドで使用された変数のプライマリメモリコピーが保存されている.スレッドは変数に対するすべての操作をワークメモリで行わなければならないが、メインメモリの変数を直接読み書きすることはできない.ワークメモリはスレッド間で独立しており、スレッド間の変数値の伝達はメインメモリで行う必要がある.
volatileキーワード
普段jdkソースを読むとき、よくソースコードに書かれた変数がvolatileキーワードで修飾されているのを見ますが、このキーワードをクリアするのに何の役に立つのか、今やっと分かりました.では、このvolatileが何の役に立つのかを話しましょう.
1つの変数がvolatileとして定義されると、この変数のすべてのスレッドに対する可視性が保証されます.すなわち、1つのスレッドがこの変数の値を変更した場合、変数の新しい値は他のスレッドにとってすぐに知ることができます.volatile変数に対するすべての書き込み操作は,すぐに他のスレッドに知られると理解できる.しかし、これはvolatile変数に基づく演算が並列に安全であることを意味するものではありません.volatileはメモリの可視性しか保証できませんが、変数の操作に対する原子性は保証されていません.たとえば、次のコードがあります.
/** *   20   ,     race    10000     ,          , *    race     200000,           200000。 * * @author Colin Wang * */
public class VolatileTest {
    public static volatile int race = 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; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {

                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }

        while (Thread.activeCount() > 1)
            Thread.yield();

        System.out.println(race);
    }
}

これは、race++操作が原子操作ではないため、変数raceに対するスレッドの変更が失われることがあります.volatale変数を使用するには、一般的に次の2つのシーンに一致します.
  • 変数の演算結果は、変数の現在の値に依存しないか、単一のスレッドのみが変数の値を変更することを保証することができる.
  • 変数は、他の状態変数と共に不変制約に関与する必要はありません.

  • volatile変数を使用すると、JITコンパイラの命令再ソート最適化を禁止することもできます.ここでは、単一のモードを使用して例を挙げます.
    /** *         * * @author Colin Wang * */
    public class Singleton_1 {
    
        private static Singleton_1 instance = null;
    
        private Singleton_1() {
        }
    
        public static Singleton_1 getInstacne() {
            /* *          instance==null   ,           。 *                    ,     ,          。 *          ,      ,          : *                   ,               。 */
            if (instance == null) {
                synchronized (Singleton_1.class) {
                    if (instance == null) {
                        /* *        :               ,JVM        ,         : * 1. instance    ,          null * 2.  Singleton_1     ,      ,      * 3. instance           *          ,         1,3,2, *           1,3  ,        , *   instance    null ,      。 *   2      ,                 。 */
                        instance = new Singleton_1();
                    }
                }
            }
            return instance;
        }
    }
    /** *         * * @author Colin Wang * */
    public class Singleton_2 {
    
        /* *     JIT              ,    volatile   , *                           , *                 ,             。 */
        private volatile static Singleton_2 instance = null;
    
        private Singleton_2() {
        }
    
        public static Singleton_2 getInstacne() {
            if (instance == null) {
                synchronized (Singleton_2.class) {
                    if (instance == null) {
                        instance = new Singleton_2();
                    }
                }
            }
            return instance;
        }
    }

    変数はvolatile修飾後、変数の変更にはメモリバリアの保護があり、後続の命令がメモリバリアの前の位置に並べ替えられないようにします.volalite変数の読み取り性能は通常の変数と似ていますが、メモリバリア命令を挿入してプロセッサが乱順に実行されないことを保証する必要があるため、書き込み性能は低くなります.それでも、ほとんどのシーンではvolatileの総オーバーヘッドがロックよりも低いため、volatileの意味が需要を満たす場合、volatileを選択することはロックを使用するよりも優れています.
    この文書は
    Javaメモリモデルとvolatileキーワード