多くのオンライン資料は、主要なメモリがあることを導入するJavaメモリモデルを説明し、各ワーカースレッドには独自の作業メモリがあります。メインメモリには1つのデータがあり、ワーキングメモリには1つの部分があります。ワーキングメモリとメインメモリの間には、同期するためのさまざまな原子操作があります。
次の写真はこのブログからです
ただし、Javaバージョンの連続的な進化により、メモリモデルも変更されています。この記事では、Javaメモリモデルのいくつかの機能についてのみ説明しています。新しいメモリモデルであろうと古いメモリモデルであろうと、これらの機能を理解すると明確に見えます。
1。原子性
原子性とは、操作が途切れやすいことを意味します。複数のスレッドが一緒に実行された場合でも、操作が開始されると、他のスレッドによって邪魔されません。
一般に、CPUの指示は原子動作であると考えられていますが、私たちが記述するコードは必ずしも原子動作ではありません。
たとえば、i ++。この操作は原子操作ではなく、基本的に3つの操作に分割され、Iを読み取り、+1を実行し、iに値を割り当てます。
2つのスレッドがあるとします。最初のスレッドがi = 1を読み取ると、+1操作はまだ実行されておらず、2番目のスレッドに切り替えられます。この時点で、2番目のスレッドはi = 1も読み取ります。次に、2つのスレッドが後続の+1操作を実行してから、値を割り当てます。私は3ではなく2です。明らかにデータに矛盾があります。
たとえば、32ビットJVMで64ビットの長い値を読むことは原子操作ではありません。もちろん、32ビットJVMは32ビット整数を原子操作として読み取ります。
2。注文
並行性の間、プログラムの実行は故障している可能性があります。
コンピューターがコードを実行する場合、必ずしもプログラムの順に実行されるわけではありません。
class orderexample {int a = 0;ブールフラグ= false; public void Writer(){a = 1; flag = true; } public void reader(){if(flag){int i = a +1; }}}たとえば、上記のコードでは、それぞれ2つのスレッドで2つの方法が呼び出されます。 Common Senseによれば、ライティングスレッドは最初にa = 1を実行し、次にflag = trueを実行する必要があります。読み取りスレッドが読み取られているとき、i = 2;
ただし、a = 1およびflag = trueであるため、論理相関はありません。したがって、実行の順序を逆にすることが可能であり、最初にflag = trueを実行し、次にa = 1を実行することができます。この時点で、flag = trueの場合、読み取りスレッドに切り替えます。現時点では、a = 1はまだ実行されていませんが、読み取りスレッドはi = 1になります。
もちろん、これは絶対的ではありません。故障していない可能性があり、起こらない可能性があります。
では、なぜ故障していないのですか?これは、CPU命令から始まります。 Javaのコードがコンパイルされた後、最終的にアセンブリコードに変換されます。
命令の実行は、多くのステップに分けることができます。 CPU命令が次の手順に分割されていると仮定する
ここに2つの指示があるとします
一般的に、命令は連続的に実行され、最初に命令1を実行し、次に命令2を実行すると考えます。各ステップに1 cpu期間が必要であると仮定すると、これら2つの命令を実行するには10 cpu期間が必要です。実際、指示は並行して実行されます。もちろん、最初の命令がIFを実行する場合、命令が登録されているなどが同時に占有できないため、2番目の命令が実行できない場合。したがって、上の図に示すように、2つの指示は比較的ずれた方法で並行して実行されます。命令1がIDを実行すると、命令2はifを実行します。このようにして、2つの指示がわずか6 cpu期間で実行されましたが、これは比較的効率的でした。
このアイデアによれば、A = B+Cの命令がどのように実行されるかを見てみましょう。
図に示されているように、ADD操作中にアイドル(x)操作があります。BとCを追加する場合、図のx操作を追加するとき、Cはメモリから読み取られていないため(CはMEM操作が完了したときにメモリからのみ読み取られます。ここでは、R2からR2からR1とR1の順にr1とR1の順に、R1とR1が追加できますか?ハードウェアなので、ADDが実行される前にWBが実行されるのを待つ必要はありません)。したがって、ADD操作にはアイドル(x)時間があります。 SW操作では、ex命令をADD Ex命令と同時に実行できないため、アイドル(x)時間があります。
次に、少し複雑な例を見てみましょう
a = b+c
d = ef
対応する指示は次のとおりです
その理由は上記に似ているので、ここでは分析しません。ここにはXがたくさんあることがわかりました。また、サイクルが無駄になるのに多くの時間の時間があり、パフォーマンスも影響を受けます。 XSの数を減らす方法はありますか?
Addには上記の命令があるため、データ依存関係があるため、Xの自由時間を埋めるためにいくつかの操作を使用したいと考えています。データ依存関係のない命令を使用して、データ依存関係によって生成された自由時間を埋めることを望んでいます。
指示の順序を変更しました
指示の順序を変更した後、xは排除されます。全体的な実行期間も減少しています。
命令の並べ替えにより、パイプラインがよりスムーズになります
もちろん、命令の再配列の原則は、シリアルプログラムのセマンティクスを破壊できないということです。たとえば、a = 1、b = a+1、そのような命令は再配置されません。なぜなら、再配置のシリアル結果は元の命令とは異なるためです。
命令の再配列は、コンパイラまたはCPUを最適化する方法にすぎません。この最適化により、この章の冒頭でプログラムに問題が発生しました。
それを解決する方法は?揮発性キーワードを使用して、この後続のシリーズが導入されます。
3。可視性
可視性とは、スレッドが共有変数の値を変更するときに他のスレッドがすぐに変更を知ることができるかどうかを指します。
視界の問題は、さまざまなリンクで発生する場合があります。たとえば、前述の命令の並べ替えも視認性の問題を引き起こします。さらに、コンパイラの最適化または特定のハードウェアの最適化も視認性の問題を引き起こします。
たとえば、スレッドは共有値をメモリに最適化し、別のスレッドが共有値をキャッシュに最適化します。メモリ内の値を変更する場合、キャッシュ値は変更を知りません。
たとえば、いくつかのハードウェアの最適化は、プログラムが同じアドレスに複数回書き込むと、それが不要であり、最後の書き込みのみを保持すると思われるため、以前に記述されたデータは他のスレッドでは見えません。
要するに、視界の問題のほとんどは最適化に起因しています。
次に、Java仮想マシンレベルから生じる可視性の問題を見てみましょう
問題はブログから来ています
パッケージedu.hushi.jvm; /** * * @author -10 * */public class visibilityTestはスレッド{private boolean stop; public void run(){int i = 0; while(!stop){i ++; } system.out.println( "finish loop、i =" + i); } public void stopit(){stop = true; } public boolean getStop(){return stop; } public static void main(string [] args)exception {visibilitytest v = new visibilityTest(); v.start(); thread.sleep(1000); v.stopit(); Thread.Sleep(2000); system.out.println( "Main" Finish "); System.out.println(v.getStop()); }}コードは非常に簡単です。 Vスレッドは、メインスレッドがSTOPメソッドを呼び出すまでI ++をwhileループに保持し、VスレッドのSTOP変数の値を変更してループを停止します。
一見単純なコードが実行されたときに問題が発生します。このプログラムは、スレッドがクライアントモードで自己侵入操作を行うのを止めることができますが、サーバーモードでは、最初に無限のループになります。 (サーバーモードでのJVM最適化の詳細)
64ビットシステムのほとんどはサーバーモードで、サーバーモードで実行されます。
メインを仕上げます
真実
これらの2つの文のみが印刷されますが、仕上げループは印刷されません。ただし、停止値はすでに真実であることがわかります。
このブログの著者はツールを使用してプログラムをアセンブリコードに復元します
ここでは、アセンブリコードの一部のみが傍受され、赤い部分はループ部分です。 0x0193bf9dのみが停止検証であることが明確にわかりますが、赤い部分は停止値を取得しないため、無限のループが実行されます。
これはJVM最適化の結果です。それを避ける方法は?指令の並べ替えと同様に、揮発性キーワードを使用します。
揮発性が追加されている場合は、アセンブリコードに復元すると、各ループが停止値を取得することがわかります。
次に、「Java言語仕様」の例をいくつか見てみましょう
上記の図は、命令の並べ替えが異なる結果につながることを示しています。
上記の図でR5 = R2が作成されている理由は、R2 = R1.x、R5 = R1.xであり、コンパイル時にR5 = R2に直接最適化されているためです。最終的に、結果は異なります。
4。前に起こる
5。スレッドの安全性の概念
これは、特定の関数または関数ライブラリがマルチスレッド環境で呼び出された場合、各スレッドのローカル変数を正しく処理し、プログラム機能を正しく完了できるようにすることができるという事実を指します。
たとえば、最初に言及されたi ++例
これにより、スレッドが不安定になります。
Thread Safetyの詳細については、前に書いたこのブログを参照するか、次のシリーズに従ってください。関連するコンテンツについても説明します。