記事のディレクトリ
1.揮発性の紹介
揮発性はJVMが提供する最も軽量な同期メカニズムと言えますが、完全に正しく理解することは容易ではなく、スレッドの同期を完全に保証することはできません。つまり、特定の条件下では、揮発性はスレッドの同期を保証できます。 。これにより、多くのプログラマーは無意識のうちにそれを使用しないようになります。したがって、より安全な同期キーワードを選択してください。ただし、Volatileは多くの場所で使用されており、マルチスレッドの他の多くの操作を理解することも非常に重要です。
2.Volatileの役割1-可視性
** JMM(Javaメモリモデル)**を理解していれば、次のことがわかります。特別な状況がない場合、メインメモリでのスレッドの共有変数の読み取りと書き込みは、スレッド自体の作業メモリで行われます(簡単に理解できます)。ワーキングメモリはキャッシュと同等です。したがって、スレッドが共有変数を変更すると、他のスレッドはすぐにそれを理解できません。volatileキーワードを使用して変数を変更した後、スレッドはVolatile変数を変更します。他のスレッドの場合は、次のようになります。すぐにわかります。Volatileの可視性のみです。以下は、表示する比較です。
public class Volatile
{
static boolean flag = true;//定义了一个全局变量
public static void main(String[] args) throws InterruptedException
{
Thread thread1=new Thread(new Runnable() {
@Override
public void run()
{
while (flag)//当flag为true的时候会一直执行
{
}
}
});
thread1.start();//开启第一个线程
Thread.sleep(1000);//保证第一个线程一定是比第二个线程先执行
new Thread(new Runnable() {
@Override
public void run()
{
System.out.println("stop thread1");
flag=false;//将全局变量设置为false
}
}).start();
}
}
このコードを実行した後、2番目のスレッドがflag = false;の操作を実行した場合でも、最初のスレッドは実行を継続することがわかります。スレッド2の作業領域のフラグはまだtrueであるため、正しいです。その他スレッドはフラグの変更について知りません。
しかし、フラグをボラティリティとして宣言すれば、そのような問題は発生しません。
3.Volatileの役割2-命令の並べ替えを禁止する
命令の並べ替えとは、単一のスレッドで、最終結果に影響を与えることなく、JVMがコードを順不同で実行してシステムリソースを最大限に活用することを意味します。
public class ThreadTest
{
static int b,c;
static int a;
private static class ReaderThread extends Thread
{
@Override
public void run()
{
a=1; b=2; c=a+b;
}
}
}
このコードでは、命令を並べ替えることができます.a = 1とb = 2の演算の順序は、これら2つの演算の順序が逆になっても実行されないため、必ずしもコードの順序で実行されるとは限りません。最終結果に影響しますが、c = a + bです。この操作は、a = 1およびb = 2の後に実行する必要があります。これにより、最終結果が正しいことが保証されるためです。
1つのスレッドでの並べ替えは最終結果に影響しませんが、別のスレッドがこのスレッドの中間結果に依存している場合、エラーが発生する可能性があります。揮発性の場合、メモリバリアを介した命令の並べ替えが禁止される可能性があります。
public class ThreadTest
{
static int b,c;
volatile static int a;
private static class ReaderThread extends Thread
{
@Override
public void run()
{
a=1;//内存屏障,保证在这之后的代码不会重排序到内存屏障之前
b=2; c=a+b;
}
}
}
4.揮発性主成分分析
揮発性変数の読み取りと書き込みを行うコンパイル済みコードを見ると、通常の変数と比較してもう1つの操作があることがわかります:lock addl $ 0x0、(%esp)。この操作は実際にはメモリバリアと同等です(メモリバリア)。ロックプレフィックスがキーです。次のaddl $ 0x0、(%esp)は操作なしであり、効果はありません。
揮発性物質の可視性を保証し、並べ替えを禁止するのはロック操作です。
lcok操作の機能は、このスレッドの作業メモリーの値をメインメモリーに書き込むことです。この操作には2つの効果があります。
- 他のスレッドの作業メモリが無効になります。つまり、揮発性変数に対する他のスレッドの読み取りおよび書き込み操作は、メインメモリから直接取得する必要があります。これにより、可視性が確保されます。
- ワーキングメモリのキャッシュをメインメモリに書き込むことは、JVMに指示することと同じであるため、これらの操作を完了しました。後続の操作を自分の前で完了できないため、メモリバリアが作成されます。
5.揮発性は原子性を保証できません。
Atomicityは、単に操作が分割できないことを意味します。ただし、volatileは、変数の操作がアトミックであることを保証しません。例を見てみましょう。揮発性変数aがあり、それに対してa ++操作を実行するとします。表面上はa ++は操作です。しかし、3つのステップがあります。
- の初期値を読み取ります(揮発性は、読み取りが最新の値でなければならないことを保証します)
- a +1を計算する
- a + 1の値をaに割り当てます(揮発性は、割り当てられたaがメインメモリに書き込まれることを保証します)
考えてみてください。a+ 1を計算する2番目のステップで、他のスレッドがaの値を変更した場合、最終結果は間違っています。
6.揮発性のシナリオを使用する
volatileは原子性を保証できないため、同期に適用する場合には一定の制限があります。次の2つのルールを満たさない操作シナリオでも、他の方法で原子性を確保する必要があります。
- 操作の結果は変数の現在の値に依存しないか、単一のスレッドのみが変数を変更できることが保証されます。
- 変数は、他の状態変数との不変制約に参加する必要はありません。
同期には特定の制限があります。次の2つのルールを満たさないコンピューティングシナリオでは、原子性を確保するために他の手段を使用する必要があります。
- 操作の結果は変数の現在の値に依存しないか、単一のスレッドのみが変数を変更できることが保証されます。
- 変数は、他の状態変数との不変制約に参加する必要はありません。