記事のディレクトリ
アウトライン
Java言語仕様第3版では、volatile
次のようにキーワードが定義されています。
Javaプログラミング言語は、アクセス共有変数へのスレッドは、変数が正確で一貫して使用して更新を確保するために、スレッドは排他ロックによって保証されるべきで共有することができるだけでは、この変数を受け取ることができます
並行プログラミング、2つの非常に一般的なキーワードがありますsynchronized
し、volatile
volatile
変数を変更するために使用することができますので、同時実行のすべてのスレッドがこのオブジェクトの値を取得することと同じです
比較するとsynchronized
ロックが何かを見つけるときの動作、volatile
それはそう、彼の使用することを、コンテキストスイッチのプログラムを起こさない、変数の可視性を確保するために、リアルタイムで共有変数値によるものであるが、ロック何もしませんでしたこれも、揮発性の軽量の同期と
揮発性2最大の特徴は以下のとおりです。
- だから、取得したすべてのスレッドの値が統一されているメモリモデル(可視性)
- 最適化のメカニズムやエラーを並び替えるので、実行中の命令を避けてください
可視
内存可见性
:いくつかの変数が同じで参照するには、各ワーカースレッドの値が、彼の最新のステータス
なぜ、時には目に見えません
これは、まず最初に、コンピュータのキャッシュから開始することです。
- 昔、コンピュータのCPUとメモリが直接接続されているが、これは、CPUの速度を維持するための伝送速度によるものです
- 転写ステーションに似て小容量より高速な読み取り速度の容器、間に入れてキャッシュセットを経由して、後でコンピュータ、
- それでも、後に、彼らはマルチレベル・キャッシュに開発を続け、より多くのではない上のキャッシュ、およびので:L1、L2、L3 ...
- その後、マルチコアへのコンピュータは、各コアは、独自のマルチレベルキャッシュのキャッシュを持っています...
おそらく、このような写真:
だから、L3両方Core1,2,3,4使用される変数は、コアに対応するL1またはL2キャッシュの各々にコピーを所有するこの変数の値をコピーする時間がある場合
マルチスレッド実行の場合ならば、変更される変数に複数のコアを有することが可能であるが、最初の彼らは、次の例のように、データは、それらのL1またはL2キャッシュから実際に変更します。
変更CORE1の値は0から100まで変化さ
コア2は、独自のL1から読み出した値であり、変更は50であります
値はバック共有L3中のL1から書き込まれ、コア2
値はバック共有L3中のL1から書き込まれるCORE1
最終結果は、100の代わりに、50(また、コア2とリード100の代わりに0であると考えられます)
これは目に見えない問題であり、
解決方法
メインメモリに戻すように強制的に変更され書かれた全体LOCKで組み付け前にして、MESIプロトコル??? CPUバススニッフィングメカニズムをトリガー:OSとJVMより野菜ので、その具体的な単語なので、補足した後、ここでは最初の詳細に話をしませんか???しかし、一番下には、まだ割り当ての時にLOCKアトミック操作を使用できますか???
volatileキーワード書き込み操作を使用した後は、JITコンパイラのステージは、このようなことをするだろう:ライトバックキャッシュの現在の目標値を、そしてそのように、信号(いないことを確認し???)を送信する他のCPUすべての障害のキャッシュ内のデータは、ので、この値を使用している他のCPUは、L3キャッシュは、彼らが取得しますので、データを取得するために行かなければならないであろうと、最新のデータであり、
だから、あなたが読んたびに最新の値にメインメモリから取得することvolatile変数の性を保証は、メインメモリの各リフレッシュ値の後に直接書き込まれます
しかし、これは唯一のオペレーティングシステムレベルで、Java仮想マシンのために、彼はまた彼自身のメモリモデルを持っています
そのようにすることも同様ので、各ワーカースレッドが最新の値を取得することを長い間その値などのような揮発性のJavaメモリモデル、または類似し、そこに、
防止の並べ替え
並べ替え、あなたがプログラムを書くことを、コードのどのような数行の間に関係がないことをシステムの実装では、あなたが実行する順序を逆にすることができますされ、その後、彼は実行するのが最善と思ったため(いわゆる混乱をたどりますオーダー実行)
興味深い例
public class OutofOrderExecution {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args)
throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
public void run() {
a = 1;
x = b;
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
b = 1;
y = a;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("(" + x + "," + y + ")");
}
}
彼の出力は、結果が(0,0)または(1,1)または(1,0)または(0,1)となります
= 1、X = Bと、これら二つの文例えば、Aが注文の排除と一致しているので、なぜ、それは交換の実装可能です...
どのような並べ替え引き起こします
もののためにその最も古典的なDCLのシングルトンの間違った言葉遣いを持ちます:
public class Singleton {
private static Singleton instance = null;
private Singleton (){
}
public static Singleton getInstance() {
if(instance==null)
{
synchronized(Singleton.class)
{
if(instance==null)
{
instance= new Singleton();
}
}
}
}
return instance;
}
この場合、問題はにつながる可能性がある:一緒にマルチスレッドの最初の呼び出し、一つは「半分のオブジェクト」を得るかもしれません
問題は、これらの言葉のそれであります:
instance = new Singleton();
彼はより小さな別個のステップに分割その後に行われることができる、JVMの語句アトミック操作、すなわちありません。
//分配内存空间
memo = allocate();
//调用init方法,执行一系列比如加载元数据之类的操作
init(memo)
//传递引用
instance = memo
あなたが実際に実行すると、第三ステップの実行順序は、最初の2つのステップとは無関係です。しかし、最初の文の前に彼を置くことが可能である仮想マシンは、このスレッドのために、とにかく第2文の前に配置することができます彼がそうする可能性があるので、それらは、同じであること
しかし、他のスレッドで同じではありません
たとえば、仮想マシンの実行順序は、今ちょうどオブジェクトをインスタンス化し始めている、このようになります。
//分配内存空间
memo = allocate();
//传递引用
instance = memo
//调用init方法,执行一系列比如加载元数据之类的操作
init(memo)
その後、別のスレッドBは、ちょうどこのメソッドを呼び出すには、この時間があります
public static Singleton getInstance() {
if(instance==null) <-----------另外一个线程B在这里
{
synchronized(Singleton.class)
{
if(instance==null)
{
instance= new Singleton(); <------------A线程在这里初始化到一半,刚分配完内存地址
}
}
}
return instance;
}
このとき、インスタンス==メモ!= NULLので、その結果が得られた場合はfalseで、直接オブジェクトの半分スレッドBの完全な具体化だけでなく、実際に到達するためにつながった、戻りインスタンスをトリガーします
メモリバリア
OSとJVMより野菜以来、私は、Javaが起こり、前のようなので、補足した後、最初のここでは詳細に話をしないように、
ことでvolatileキーワード内存屏障
の方法は、上記の問題を解決することが可能です
この制限のような、バック実行するために、すべての上に実行しなければならない前に、境界操作の分割は、命令の束:メモリである話すの表面から遮蔽しました
A
B
C
D
-----メモリバリア
E
F
G
上記のいくつかのABCDの場合は、それらの間の実行順序を簡単にコンパイラによって調整することができますが、彼らは実行が終わった後まで待つ必要があり、その後、プログラムはEFGを実行するために下がるだろう
volatileキーワードを使用して、いつ、コンパイル時に、コンパイラは、メモリバリアを挿入します
揮発性メモリセマンティクスを達成するために、生成バイトコードは、プロセッサ並べ替えの特定の型を阻害する命令シーケンスメモリバリアに挿入されるコンパイラ。しかしながら、コンパイラ、障壁を最小化するために、挿入の総数の最適な配置を見出すことは、保守的な戦略を取ることが、Java(登録商標)メモリ・モデルはほとんど不可能です。
以下は、保守的な戦略JMMメモリバリア挿入戦略に基づいています。
- 各先行する書き込み動作は、揮発性StoreStoreバリアを挿入します。
- ライトバック動作は、揮発性StoreLoadバリアのそれぞれに挿入されています。
- 各読み出し動作後の揮発性LoadLoadバリアを挿入します。
- 各読み出し動作後の揮発性LoadStoreバリアを挿入します。
したがって、DCL正しい言葉遣いが原因彼は3つの文が自動的に再ソートすることができない行動を開始したメモリバリア、インスタンスのポインタに、コンパイル時に、そう、揮発性の属性インスタンス変数を追加するべきである上に、と述べましたメモリ空間を指した後、初期設定である必要がありますが完了すると、彼は半分の問題は、オブジェクトが登場していません
不可分性
アトミックとは何ですか
原子
意図はありません:もはや小さな粒子を分割する(手動クォークの存在を無視します)
中断されたステップや操作のシリーズですされていません原子操作
例えば:
//赋值操作
a = 1
しかし、インクリメント操作がアトミックではありません!
count++;
新しいインスタンスは、上記の例と同様であり、++は、いくつかのステップに分割されます数:最初の値を取得し、その後増加し、割り当て
だから、: 自增不是原子操作!!!
(吸着原子のAtomicIntegerクラスからお考え下さい)
アトミックを有していない揮発性変数
時間の一定量概念CPUタイムスライスという男、各スレッドがCPUを使用するように、時間は、他の人に制御を与えるようにする可能性がある場合、上記インクリメント演算子としてではないアトミック操作( )、他人の手を切るためにそれを使用する権利、割り当てを行うのに十分な時間がない、半分を実行する可能性があります
前記の前に、原因を共有する能力を持っている揮発性の変数に自分自身を作ることであるが、揮発性変数は、例を見アトミックである必要はありませんが、そう言って、ロックを使用しませんでした。
増加が割り当てられていないので、揮発性変数の初期値は0の値がまだあるが、中間変数が1つのプランの値が存在する(、スレッドときに実行半分でインクリメント演算子0であります、)彼に割り当てを与えた他の誰かにCPUの制御を切り替え、まず、他の人の値を読み取るために、この時間は非常にアトミックである、0で、今して他人の手のコントロールに割り当てられ、他の人がこの期間中に変更することができますこの変数は、他の人が変数に別の値割り当てられている場合:100 CPU、その後、カットバックをし、終了する、割り当てられた値なし、最終結果ので、彼は1で、それは誰か他の人の割り当てです無効
しかし、私は後で別の引数を見ました:例えば、揮発性の2時間を増加し、少しは最初、その後、彼はロックのアクションを実行する少しがある場合は、ライトバックし始めている、そしてメカニズムを盗聴CPUバストリガーMESIは、聞かせてもう一つは失敗に直接書く時間がありませんでした?
私はその後、いくつかの上場まで使用しているオンラインをお見せするためのコードを:
package com.imlehr.juc.chapter1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class NotAtomic {
public static volatile int num = 0;
public static void main(String[] args) throws InterruptedException {
Runnable r = ()->{
for(int j=0;j<10000;j++) {
num++;
}
};
//先不管那么多(虽然我知道这样创建线程池不好)
ExecutorService e = Executors.newFixedThreadPool(100);
for(int i=0;i<20;i++)
{
e.execute(r);
}
//等任务全部执行完
TimeUnit.SECONDS.sleep(20);
//输出结果
System.out.println(num);
}
}
これは、20件のスレッドの増加NUM万を作るために、各スレッドで、とにかく、結果は必ずしも20 * 10000と等しくありません
だから、要約、あなたは揮発性変数は原子であるこれらの操作をしたい、あなたはロック(またはのAtomicIntegerは揮発性が異なる可能性が置き換えシリーズに、上記の揮発性int)を使用して検討すべきです
参考記事:
https://monkeysayhi.github.io/2016/11/29/volatileキーワードの役割、原則/