変数a = 1がメモリにあるとすると、aの新しい値を計算する必要があります。
このプロセスでは、CPUの操作によって操作されます。計算プロセスは、メモリから変数aのコピーを作成し、計算後、戻り値をメモリの変数aに書き換えます。(計算にはCPUが使用され、ストレージにはメモリが使用されます。この機能により、計算プロセスはデータのコピーをCPU計算にコピーし、結果をメモリに返します)
上記のプロセスはスレッド操作に相当します。操作を実行する複数のスレッドがある場合、いくつかの問題が発生します:
各スレッドはメモリ内のデータを直接操作するのではなく、データのコピーをコピーするため、次の問題があります。
メモリの可視性:
メモリは、メモリデータを読み取って計算し、メモリに再書き込みします。このとき、他のスレッドは、発生した状態変化をすぐに確認できます。メモリの可視性
可視性エラー:読み取り操作と書き込み操作が異なるスレッドで実行された場合、読み取り操作を実行するスレッドが他のスレッドによって書き込まれた値をタイムリーに参照できることを保証することは不可能です。場合によっては不可能です。
マルチスレッド開発中にメモリの可視性と可視性のエラーが頻繁に発生する
この時点で、同期を使用してオブジェクトが安全に解放されるようにすることができます。さらに、より軽量な揮発性変数を使用することもできます
揮発性キーワードの紹介:
Javaは、他のスレッドに変数の更新操作が確実に通知されるように、弱い同期メカニズム、つまり揮発性変数を提供します。
volatileは軽量ロックと考えることができます。ただし、ロックとは少し異なります。
- マルチスレッドの場合、相互に排他的な関係ではありません
- 可変状態の「アトミック操作」は保証できません
揮発性の役割:複数のスレッドがデータメモリにアクセスするという目に見えない問題を解決する
コード例:
Runnableインターフェースを実装する以下のクラスがあり、runメソッドはフラグの値を変更します
class ThreadDemo implements Runnable {
private boolean flag = false;//未加任何修饰
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
flag = true;//修改flag的值
//修改成功后输出flag的值
System.out.println("flag=" + isFlag());//调用isFlag返回flag值,并打印出来
}
public boolean isFlag() {
return flag;
}
}
以下のTestVolatileのmainメソッドを実行します
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();// 启动线程会执行run方法,然后this.flag=true
while(true){
if(td.isFlag()){//如果前面的线程修改flag成功并且通过isFlag返回为true则打印分割线
System.out.println("------------------");
break;//结束循环
}
}
}
}
得られた結果:
新しいThread(td).start();ここでは、flag = trueを出力します
その後、次のwhile(true)のif(td.isFlag())は常にfalseになるため、無限ループが発生し、分割線のコードは実行されません。
なぜこんな風に出たの?前のスレッドがフラグの値を変更していませんか(flag = true)?次にisFlag()はtrueを返しませんか?
次に、System.out.println( "------------------")を実行して、ループを終了しますか?
実行結果のgifは次のようになります。出力フラグ= trueのみに分割線はありません----------------
もう疑問に思いましたか?
理由に答える:図面の説明
図に示すように、最初のスレッドはフラグ値を変更し、メインメモリ内のフラグ値を変更しますが、whileループでは、スレッドにキャッシュがあるためにスレッドがフラグを変更し、キャッシュ内のフラグが常にfalseであるため、常にフラグが変更されます。無限ループ。
それでは、問題を解決するにはどうすればよいですか?
方法1:同期(td)を追加してtdクラスをロックして解決する、つまり、図に示すようにwhileループで
while(true){
synchronized (td){
if(td.isFlag()){
System.out.println("------------------");
break;
}
}
}
同期(td)を追加した後の実行効果は、下のgif画像に示すとおりです。
方法2、揮発性修飾子をフラグ変数に追加する
つまり、変数の前にvolatileを追加します
それで問題は来ていますか?どっちがいい?2つの方法の長所と短所は何ですか?
上記のコードで
方法1は、同期を使用してtdオブジェクトをロックするため、whileループに入るたびに、メインメモリに移動して、tdオブジェクトにロックがあるかどうかを判別する必要があります。このとき、キャッシュはメインメモリから更新されます。それ以外の場合、ロックはスレッドがtdオブジェクトを操作する場合は、ロックがあるかどうかを判断し、ロックがある場合はブロックします。非常に低い効率
Volatileは、メインメモリー内のフラグオブジェクトを直接操作して、複数のスレッドのメモリー内のデータが見えるようにすることと同じです。
揮発性があると、根本的な並べ替えが失敗します。効率は比較的低いですが(上記のいずれも追加されていないときと比較して)、ロック方式よりも効率的です。
では、volatileはどのような状況でも使用できますか?答えはいいえだ
volatileには「相互排除」と「原子性」がないため、ロックには相互排除と原子性があるため