序文
時には、1つまたは2つのインスタンスフィールドを読み書きするためだけに同期を使用する場合、高すぎると思われます。揮発性キーワードは、インスタンスフィールドへの同期アクセスのためのロックフリーメカニズムを提供します。ドメインが揮発性として宣言されている場合、コンパイラと仮想マシンは、ドメインが別のスレッドによって同時に更新される可能性があることを知っています。揮発性キーワードについて話す前に、メモリモデルの関連する概念と、同時プログラミングの3つの特性という3つの特性を理解する必要があります。
1。原子性、可視性、秩序性を備えたJavaメモリモデル
Javaメモリモデルは、すべての変数がメインメモリに存在することを規定しており、各スレッドには独自の作業メモリがあります。変数上のスレッドのすべての操作は、ワーキングメモリで実行する必要があり、メインメモリで直接動作することはできません。また、各スレッドは他のスレッドの作業メモリにアクセスできません。
Javaで、次のステートメントを実行します。
int i = 3;
実行スレッドは、まず、変数Iが独自の作業スレッドにあるキャッシュラインを割り当て、次にメインメモリに書き込む必要があります。値3をメインメモリに直接書き込む代わりに。
それでは、Java言語自体が原子性、可視性、秩序を保証するものは何ですか?
原子性
基本データ型の変数の読み取りおよび割り当て操作は、原子操作です。つまり、これらの操作は中断することはできず、実行するかどうかです。
次のコードを見てみましょう。
x = 10; //ステートメント1y = x; //ステートメント2x ++; //ステートメント3x = x + 1; //ステートメント4
ステートメント1のみが原子操作であり、他の3つのステートメントのいずれも原子操作ではありません。
ステートメント2には、実際には2つの操作が含まれています。最初にxの値を読み取り、次にxの値をワーキングメモリに書き込む必要があります。 Xの値を読み取り、ワーキングメモリにXの値を書き込む2つの操作は原子動作ですが、それらは一緒に原子操作ではありません。
同様に、x ++およびx = x+1に含まれる3操作:xの値を読み取り、1の操作を実行し、新しい値を書き込みます。
言い換えれば、単純な読み取りと割り当てのみ(および変数に割り当てる必要があり、変数間の相互割り当ては原子操作ではありません)は原子動作です。
java.util.concurrent.atomicパッケージには、非常に効率的なマシンレベルの命令(ロックではなく)を使用して、他の操作の原子性を確保するクラスがたくさんあります。たとえば、AtomicIntegerクラスは、メソッドの増分とDectrementAndgetを提供します。 AtomicIntegerクラスは、同期なしで共有カウンターとして安全に使用できます。
さらに、このパッケージには、同時ツールのみを開発するシステムプログラマー向けのAtomicboolean、Atomiclong、AtomicReferenceなどの原子クラスも含まれており、アプリケーションプログラマーはこれらのクラスを使用すべきではありません。
可視性
可視性とは、スレッド間の可視性を指し、1つのスレッドの変更された状態が別のスレッドに表示されます。それがスレッドの変更の結果です。別のスレッドがすぐに表示されます。
共有変数が揮発性によって変更されると、変更された値がすぐにメインメモリに更新されることを保証するため、他のスレッドに表示されます。他のスレッドを読む必要がある場合、メモリ内の新しい値を読み取ります。
ただし、通常の共有変数は可視性を保証することはできません。これは、通常の共有変数が変更された後にメインメモリに書き込まれる場合が不確実であるためです。他のスレッドがそれを読んだ場合、元の古い値がまだメモリにある可能性があるため、視界を保証することはできません。
秩序ある
Javaメモリモデルでは、コンパイラとプロセッサは命令を再注文することが許可されていますが、再注文プロセスはシングルスレッドプログラムの実行に影響しませんが、マルチスレッドの同時実行の正確性に影響します。
揮発性キーワードを使用して、特定の「順序」を確保できます。さらに、同期とロックを使用して順序を確保できます。明らかに、同期してロックすると、各瞬間に同期コードを実行するスレッドがあることを確認します。これは、スレッドが同期コードを順番に実行させることに相当し、順序を自然に保証します。
2。揮発性キーワード
共有変数(クラスメンバー変数、クラスの静的メンバー変数)が揮発性によって変更されると、2層のセマンティクスがあります。
最初にコードを見てみましょう。スレッド1が最初に実行され、スレッド2が後で実行される場合:
//スレッド1boolean stop = false; while(!stop){dosomething();} //スレッド2stop = true;多くの人は、スレッドを中断するときにこのマークアップ方法を使用する場合があります。しかし、実際、このコードは完全に正しく実行されますか?スレッドは中断されますか?必ずしもそうではありません。おそらく、ほとんどの場合、このコードはスレッドを中断する可能性がありますが、スレッドが中断されない可能性もあります(この可能性は非常に小さくなりますが、これが起こると、死んだループが発生します)。
なぜスレッドの失敗を中断しないのですか?各スレッドには、実行プロセス中に独自の作業メモリがあります。スレッド1が実行されている場合、停止変数の値をコピーし、独自の作業メモリに配置します。次に、Thread 2がSTOP変数の値を変更したが、メインメモリに書き込む時間がなかった場合、Thread 2は他のことを実行するために、スレッド1はスレッド2の停止変数への変更について知らないため、ループし続けます。
しかし、揮発性で変更した後、それは異なります:
揮発性は原子性を保証しますか?
揮発性キーワードが操作の可視性を保証することを知っていますが、変数の動作が原子的であることを揮発性が確実にすることができますか?
パブリッククラステスト{public volatile int inc = 0; public void crossion(){inc ++; } public static void main(string [] args){最終テスト= new test(); for(int i = 0; i <10; i ++){new shood(){public void run(){for(int j = 0; j <1000; j ++)test.increase(); }; }。始める(); } //以前のスレッドが実行を完了していることを確認します(thread.activecount()> 1)shood.yield(); System.out.println(test.inc); }}このコードの結果は、実行するたびに一貫性がありません。 10,000未満の数です。前述のように、自動インクリメント操作は原子ではありません。変数の元の値を読み取り、追加の1つの操作を実行し、ワーキングメモリに書き込むことが含まれます。つまり、自己増加操作の3つのサブ操作は個別に実行される場合があります。
Variable Incの値が特定の時間に10の場合、スレッド1は変数で自己侵入操作を実行します。スレッド1は最初に変数Incの元の値を読み取り、次にスレッド1がブロックされます。次に、スレッド2は変数で自己侵入操作を実行し、スレッド2は変数incの元の値も読み取ります。スレッド1は変数Incで読み取り操作のみを実行し、変数を変更しないため、スレッド2のキャッシュ変数Incのキャッシュラインがワーキングメモリで無効になるため、スレッド2はメインメモリに直接移動してIncの値を読み取ります。 Incの値が10であることがわかった場合、1の増加を実行し、ワーキングメモリに11を書き込み、最終的にメインメモリに書き込みます。次に、スレッド1が追加操作を実行します。 Incの値は読み取られているため、スレッド1のIncの値は現時点ではまだ10であるため、スレッド1がIncを追加した後、11になり、11を書き込み、ワークメモリに書き込み、最終的にメインメモリに書き込みます。その後、2つのスレッドが自己侵入操作を実行した後、Incは1だけ増加します。
自動侵入操作は原子動作ではなく、変動性は変数の動作が原子であることを保証することはできません。
揮発性は秩序を確保できますか?
前述のように、揮発性キーワードは命令の並べ替えを禁止することができるため、揮発性はある程度秩序を確保できます。
揮発性キーワードの並べ替えを禁じられた2つの意味があります。
3.揮発性キーワードを正しく使用します
同期されたキーワードは、複数のスレッドが同時にコードを実行することを防ぎ、プログラムの実行効率に大きく影響します。揮発性キーワードのパフォーマンスは、場合によっては同期するよりも優れています。ただし、揮発性キーワードは、揮発性キーワードが操作の原子性を保証できないため、揮発性キーワードを同期キーワードに置き換えることはできないことに注意してください。一般的に言えば、揮発性を使用する場合、次の2つの条件を満たす必要があります。
最初の条件は、自己排気や自己排除などの操作にならないことです。上記のように、揮発性は原子性を保証しません。
この例を挙げましょう。それには不変が含まれています。下限は常に上限以下です。
public class numberrange {private volatile int lower、upper; public int getLower(){return lower; } public int getupper(){return upper; } public void setlower(int value){if(value> upper)throw new leglegalargumentexception(...); lower = value; } public void setupper(int value){if(value <lower)throw new IllegalArgumentException(...); upper = value; }}この方法では、スコープされた状態変数を制限するため、下部フィールドと上部フィールドを揮発性タイプとして定義しても、クラスのスレッドの安全性を完全に実装していないため、同期する必要があります。それ以外の場合、2つのスレッドがたまたまSetLowerを実行し、同時に一貫性のない値でSetupperを実行すると、範囲は一貫性がありません。たとえば、初期状態が(0、5)と同時に、コールAセットラワー(4)とスレッドBコールセット(3)である場合、これら2つの操作によって補充されている値が条件を満たしていないことは明らかです。
実際、操作の原子性が揮発性を使用するようにすることです。揮発性を使用するための2つの主要なシナリオがあります。
ステータスフラグ
Volatile Boolean ShutdownRequested; } public void dowork(){while(!shutdownRequested){// do suff}}}Shutdown()メソッドがループの外側から呼び出される可能性が高いため、つまり別のスレッドでは、シャットダウンリケストされた変数の可視性が正しく実装されるように、何らかの同期を実行する必要があります。ただし、同期されたブロックを使用してループを書くことは、揮発性のステータスフラグで書くよりもはるかに面倒です。揮発性はエンコーディングを簡素化し、ステータスフラグはプログラム内の他の状態に依存しないため、ここでは揮発性に非常に適しています。
ダブルチェックモード(DCL)
パブリッククラスシングルトン{プライベート揮発性静的シングルトンインスタンス= null; public static singleton getInstance(){if(instance == null){synchronized(this){if(instance == null){instance = new Singleton(); }} return instance; }}ここで揮発性を使用すると、多かれ少なかれパフォーマンスに影響を与えますが、プログラムの正しさを考慮すると、このパフォーマンスを犠牲にする価値があります。
DCLの利点は、リソースの利用率が高いことです。 Singletonオブジェクトは、GetInstanceが初めて実行された場合にのみインスタンス化されますが、これは非常に効率的です。欠点は、初めてロードするときに反応がわずかに遅く、並行性環境には特定の欠陥があることですが、発生の確率は非常に少ないことです。
DCLは、リソース消費、不必要な同期、スレッドの安全性などの問題をある程度解決しますが、場合によっては故障の問題、つまりDCL障害があります。 「Java Concurrencyプログラミングの実践」という本では、DCLを置き換えるために次のコード(静的な内部クラスのシングルトンパターン)を使用することをお勧めします。
パブリッククラスのsingleton {private singleton(){} public static singleton getInstance(){return singletonholder.sinstance; } private static class singletonholder {private static final singleton sinstance = new Singleton(); }}ダブルチェックについて見ることができます
4。概要
ロックと比較して、揮発性変数は非常に単純ですが、同時に非常に脆弱な同期メカニズムであり、場合によってはロックよりもパフォーマンスとスケーラビリティが向上します。他の変数や独自の以前の値から本当に独立した揮発性の使用条件に厳密に従う場合、場合によってはコードを簡素化するために同期する代わりに揮発性を使用できます。ただし、揮発性を使用したコードは、ロックを使用するコードよりもエラーが発生しやすいことがよくあります。この記事では、同期する代わりに揮発性を使用できる2つの最も一般的なユースケースを紹介します。それ以外の場合は、同期した方が適切です。
上記はこの記事に関するものです。すべての人の学習に役立つことを願っています。