[Java の基本] volatile キーワード

著者について: CSDN コンテンツ パートナーおよび技術専門家。数千万の APP を使用した日常業務にゼロから取り組んできました。
Java バックエンド、モバイル開発、人工知能などを得意とし、さまざまな分野のオリジナル シリーズ記事の共有に重点を置いています。たくさんのサポートを願っています。

1. はじめに

私たちは Java の基本を要約して学び続け、過去を振り返り、新しいことを学びます。

2. 概要

Volatile は、変数の変更に使用できる Java キーワードです。Volatile は軽量の同期化とも呼ばれ、実行時のオーバーヘッドは同期化よりも小さくなります。

2.1 機能

1. 可視性を実現するために、スレッド間で共有変数が同期されていることを確認します。
2. プロセッサの並べ替えを無効にします。

2.2 マルチスレッドシェア変数のアクセス処理

スレッドが実行されると、まずメイン メモリ データがローカル スレッドにコピーされ、操作の完了後にローカル スレッドからメイン メモリに結果がフラッシュされます。
下の図を見ると、マルチスレッドの場合、シェア変数演算の値が赤い部分になります。
ここに画像の説明を挿入

2.3 マルチスレッドで可視性の問題が発生するのはなぜですか

可視性は CPU キャッシュによって発生します。CPU はメモリとの速度差を補うためにキャッシュを増やすため、可視性の問題が発生します。
マルチスレッド環境では、複数のスレッドが同時に共有変数にアクセスしますが、異なるスレッドの実行順序と実行時間が不確実であるため、あるスレッドによる共有変数の変更が他のスレッドには表示されない場合があります。上記のシェア変数へのアクセスプロセスを参照してください。

2.4 volatile はどのようにして可視性を実現するのか

Volatile はスレッド内でのキャッシュや並べ替えを許可せず、メモリを直接変更するため、他のスレッドから見えるようになります。

volatile によって変更された変数が読み書きされると、メイン メモリに直接フラッシュされ、変数が可視になります。

2.5 命令リオーダリング禁止の実現方法

Volatile とは、コンパイラがバイトコードを生成するときに命令シーケンスに「メモリ バリア」を追加することで命令の並べ替えを禁止することです。

変数が変更されるということは、その変数が「揮発性」または「不安定」であることを意味します。これは、変数の値が他のスレッド (またはプロセス) によって変更される可能性があることを意味します。

注: volatile キーワードはスレッド間の同期のみを保証し、スレッドの安全性は保証しません。
スレッドの安全性を確保するには、synchronized キーワードや Lock インターフェイスなどの他の同期メカニズムを使用する必要があります。

Volatile は可視であり、整然としていますが、アトミックではないため、スレッドセーフではありません。

2.6 例

  • volatile キーワードは使用しないでください
禁止线程缓存变量结果。
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。
引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。
举例:
// Thread-A
new Thread("Thread A") {
    
    
    @Override
    public void run() {
    
    
        while (!stop) {
    
    
        }
        System.out.println(Thread.currentThread() + " stopped");
    }
}.start();

一个线程内使用了停止的开关,假如这个stop没有被volatile修饰,我们在线程b中修改,
线程a并不知道开关的值被修改了。
  • 並べ替えを防ぐために volatile を使用する
从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现
public class Singleton {
    
    
public static volatile Singleton singleton;
    /**
    * 构造函数私有,禁止外部实例化
    */
    private Singleton() {
    
    };
    public static Singleton getInstance() {
    
    
        if (singleton == null) {
    
    
            synchronized (singleton.class) {
    
    
                if (singleton == null) {
    
    
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

实例化一个对象其实可以分为三个步骤:
* 分配内存空间。
* 初始化对象。
* 将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
* 分配内存空间。
* 将内存空间的地址赋值给对应的引用。
* 初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。
因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。
  • volatile を使用してアトミック性を確保: 単一の読み取り/書き込み
volatile变量的单次读/写操作可以保证原子性的,如longdouble类型变量,
但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作,要保证多步的原子性,
可以通过AtomicInteger或者Synchronized来实现,本质上就是cas操作。

2.7 質問に答える

  • 質問 1: i++ はなぜ原子性を保証できないのですか?
i++其实是一个复合操作,包括三步骤:
* 读取i的值。
* 对i加1* 将i的值写回内存。 
volatile是无法保证这三个操作是具有原子性的,
我们可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。 
  • 質問 2: 共有の long 変数と double 変数に volatile を使用するのはなぜですか?
因为longdouble两种数据类型的操作可分为高32位和低32位两部分,
因此普通的longdouble类型读/写可能不是原子的。
因此,鼓励大家将共享的longdouble变量设置为volatile类型,
这样能保证任何情况下对longdouble的单次读/写操作都具有原子性

3. 原則

JVM の一番下で、volatile は「メモリ バリア」を使用して実装されます。volatile キーワードが追加されると、追加のロック プレフィックス命令が追加されます。メモリ バリアは、メモリ バリアとも呼ばれる CPU 命令です

1. javac コマンドを使用して .class ファイルをコンパイルおよび生成します
2. javap コマンドを使用して逆コンパイルし、.class ファイルの情報を表示すると、バイトコード情報にさらにいくつかの命令があることがわかります。

各プロセッサのキャッシュの一貫性を確保するために、キャッシュ整合性プロトコル (MESI) が実装されています。各プロセッサは、バス上に伝播されるデータを傍受することによって、キャッシュの値が期限切れかどうかをチェックします。キャッシュ ラインに対応するメモリ アドレスが変更されると、現在のプロセッサのキャッシュ ラインは無効な状態に設定され、プロセッサがデータを変更すると、システム メモリからプロセッサ キャッシュにデータが再読み取りされます。

volatile 変数により、各スレッドはこのようなメカニズムを通じて変数の最新の値を取得できるようになります。

3.2 使用シナリオ:

変数の可視性の要件は解決しますが、読み取り順序の要件は解決しません。

  • Volatile の機能:
    (1) volatile は変数レベルでのみ使用できます
    (2) volatile は変数変更の可視性のみを実現でき、アトミック性は保証できません volatile + cas はアトミック パッケージ下のクラスなどのアトミック性を実現します。
    (3) volatile はスレッドのブロックを引き起こしません
    (4) volatile でマークされた変数はコンパイラによって最適化されません

4. 推奨読書

[Java の基礎] 原子性、可視性、秩序性

[Java の基本] Java 可視化の前に起こること

【Javaの基礎】java-androidインタビュー 同期

[Java の基礎] java-android インタビュー - スレッドのステータス

【Javaの基礎】スレッド関連

【Javaの基礎】Java例外

[Javaの基礎] Javaリフレクション

[Javaの基礎] Javaジェネリックス

[Javaの基礎] Javaのアノテーション

[Javaの基礎] Java動的プロキシ

[Javaの基礎] Java SPI

[Javaの基礎] Java SPI II Java APT

[Javaの基礎] JVMヒープ、スタック、メソッド領域、Javaメモリモデル

おすすめ

転載: blog.csdn.net/fumeidonga/article/details/130353475