Volatile キーワードは、さまざまなスレッドで変数の値を変更するために使用されます。クラスをスレッドセーフにするためにも使用されます。これは、複数のスレッドがクラスのメソッドとインスタンスを問題なく同時に使用できることを意味します。 volatile キーワードは、プリミティブ型またはオブジェクトのいずれかで使用できます。
volatile キーワードは変数の値をキャッシュせず、常にメイン メモリから変数を読み取ります。 volatile キーワードはクラスまたはメソッドでは使用できません。ただし、変数とともに使用されます。また、可視性と順序性も保証されます。これにより、コンパイラによるコードの並べ替えが防止されます。
特定のデバイス レジスタの内容はいつでも変更される可能性があるため、そのようなアクセスがコンパイラによって最適化されないようにするために volatile キーワードが必要です。
例
class Test { static int var=5; }
上の例では、2 つのスレッドが同じクラスで動作していると想定しています。両方のスレッドは異なるプロセッサ上で実行され、各スレッドには var のローカル コピーがあります。いずれかのスレッドがその値を変更した場合、その変更はメイン メモリ内の元の値には反映されません。他のスレッドは変更された値を認識しないため、データの不整合が発生します。
class Test { static volatile int var =5; }
上記の例では、静的変数はすべてのオブジェクト間で共有されるクラス メンバーです。メインメモリにはコピーが 1 つだけあります。揮発性変数の値がキャッシュに保存されることはありません。すべての読み取りと書き込みはメイン メモリに対して行われます。
いつ使用しますか?
- Long 変数と Double 変数を自動的に読み書きしたい場合は、volatile 変数を使用できます。
- これは、Java で同期を実現する別の方法として使用できます。
- すべてのリーダー スレッドは、書き込み操作の完了後に volatile 変数の更新された値を確認します。 volatile キーワードを使用していない場合、リーダー スレッドごとに異なる値が表示される可能性があります。
- これは、複数のスレッドが特定のステートメントにアクセスすることをコンパイラーに通知するために使用されます。これにより、コンパイラーは並べ替えや最適化を実行できなくなります。
- volatile 変数を使用しない場合、コンパイラはコードの順序を変更し、メイン メモリから読み取る代わりに volatile 変数のキャッシュ値を自由に書き込むことができます。
注意事項
- volatile キーワードは変数とともに使用できます。クラスおよびメソッドで volatile キーワードを使用することは違法です。
- これにより、揮発性変数の値がローカル スレッド キャッシュからではなく、常にメイン メモリから読み取られることが保証されます。
- 変数を volatile として宣言した場合、読み取りと書き込みはアトミックになります
- これにより、メモリ整合性エラーのリスクが軽減されます。
- Java の揮発性変数への書き込みは、同じ変数の連続読み取りとの関係の前に発生を確立します。
- 揮発性変数は他のスレッドから常に参照できます。
- オブジェクト参照である volatile 変数は null の可能性があります。
- 変数が複数のスレッド間で共有されていない場合、その変数で volatile キーワードを使用する必要はありません。
同期と揮発性キーワードの違い
Volatile キーワードは synchronized キーワードの代替ではありませんが、場合によっては代替として使用できます。以下のような違いがあります。
揮発性キーワード | 同期キーワード |
---|---|
Volatile キーワードはフィールド修飾子です。 | Synchronized キーワードはコード ブロックとメソッドを変更します。 |
揮発性の場合、スレッドを待機のためにブロックすることはできません。 | 同期の場合、スレッドは待機のためにブロックされる可能性があります。 |
スレッドのパフォーマンスが向上します。 | 同期メソッドはスレッドのパフォーマンスを低下させます。 |
スレッド メモリとメイン メモリ間で一度に 1 つの変数の値を同期します。 | スレッド メモリとメイン メモリ間のすべての変数の値を同期します。 |
揮発性フィールドはコンパイラの最適化の対象になりません。 | 同期はコンパイラの最適化の対象となります。 |
揮発性キーワードの例
次の例では、カウンター値を増加させるクラスを定義しています。 VolatileThread.java の run() メソッドは、スレッドが実行を開始するときに更新された値と古い値を取得します。メインクラスでは、スレッドの配列を定義します。
VolatileData.java
public class VolatileData { private volatile int counter = 0; public int getCounter() { return counter; } public void increaseCounter() { ++counter; //increases the value of counter by 1 } }
VolatileThread.java
VolatileThread.java public class VolatileThread extends Thread { private final VolatileData data; public VolatileThread(VolatileData data) { this.data = data; } @Override public void run() { int oldValue = data.getCounter(); System.out.println('[Thread ' + Thread.currentThread().getId() + ']: Old value = ' + oldValue); data.increaseCounter(); int newValue = data.getCounter(); System.out.println('[Thread ' + Thread.currentThread().getId() + ']: New value = ' + newValue); } }
VolatileMain.java
public class VolatileMain { private final static int noOfThreads = 2; public static void main(String[] args) throws InterruptedException { VolatileData volatileData = new VolatileData(); //object of VolatileData class Thread[] threads = new Thread[noOfThreads]; //creating Thread array for(int i = 0; i <noofthreads; ++i) threads[i]="new" volatilethread(volatiledata); for(int i="0;" < noofthreads; threads[i].start(); starts all reader threads threads[i].join(); wait for } pre> <p> <strong>Output:</strong> </p> <pre> [Thread 9]: Old value = 0 [Thread 9]: New value = 1 [Thread 10]: Old value = 1 [Thread 10]: New value = 2 </pre> <hr></noofthreads;>