最近、Java の volatile キーワード検証の表示設定を確認するときに問題が発生しました。
volatile は可視性を保証します
スレッドが変数を変更するときは、まず変数をメイン メモリから作業メモリにコピーし、変更が完了した後、その変数をメイン メモリにリフレッシュします。複数のスレッドが共有変数を同時に変更している場合、1 と 2 は同時に変数を作業メモリにコピーします。1 つのスレッドが変更した後、2 のスレッドはそれを認識しません。
例:
public class VolatileDemo {
int num = 0;
public void addNum(){
this.num +=1;
}
public static void main(String[] args) {
VolatileDemo volatileDemo = new VolatileDemo();
new Thread(()->{
try{
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
volatileDemo.addNum();
},"1").start();
while(volatileDemo.num == 0){
}
System.out.println("具有可见性");
}
}
num には可視性がないため、メインスレッドは常に while でループします。
上記の例に sleep メソッドと println メソッドを追加すると、num 変数が可視になり、コードは次のようになります。
public class VolatileDemo {
int num = 0;
public void addNum(){
this.num +=1;
}
public static void main(String[] args) {
VolatileDemo volatileDemo = new VolatileDemo();
new Thread(()->{
try{
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
volatileDemo.addNum();
}).start();
while(volatileDemo.num == 0){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("num==0");
}
System.out.println("具有可见性");
}
}
理由: num が可視になる理由は 2 つあり、①は sleep メソッド、②は println メソッドです。
①:ワーキングメモリ内の変数は、スリープ前に必ずしもメインメモリに更新されるとは限らず、スリープ後にメインメモリから再ロードされない場合もあります。
Java ドキュメントのスリープとイールドの説明https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html
②:printlnメソッド内に同期ロックがあり、ロックの取得と解放によりカレントスレッドのシェア変数がメインメモリにリフレッシュされます。
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}