[マルチスレッド] -Javaメモリモデル(JMMメモリモデル)

序文

「Javaでの並行プログラミングの芸術」の最初の3つの章の内容は少し厄介です。最初の3つの章のコア知識ポイントを共有するために最善を尽くしています。これは以前と同じ考えです。は基礎のない初心者です。この本は買わないでください。「Java並行プログラミングの美しさ」を読むことをお勧めします。

1つは、Javaメモリモデルの基礎です。

スレッドのすべての人の理解を単純化するために、私は前にこのような概念を提案しました:

コーディング層で行うビジネス処理がどれほど複雑であっても、コンピューターが実行するタスクは3つだけです。

  1. メモリ内のデータを読み取る
  2. CPU計算データ
  3. 操作結果をメモリに書き戻す

スレッドタスクは、実際にはコンピューターの最下層で上記の3つのタスクを完了します。単純なコンピューターモデルを使用して、上記の関係を説明できます。
ここに画像の説明を挿入

ここで、スレッドはメインメモリからデータを取得し、それをCPUに渡して処理し、CPUの処理が終了した後にデータをメインメモリに返す役割を果たします。では、このプロセスでは、JMM(JAVAメモリモデルの変換:JAVAメモリモデル)は、データ処理の機能を実現するためにスレッドメモリモデルをどのように設計しますか?ここでは、次のJMM基本メモリモデルを見ることができます。

ここに画像の説明を挿入

ここで、スレッドAがi = 1の割り当て操作を完了したい場合は、次のプロセスを実行する必要があります。

  1. スレッドAがINT読み込み私をメインメモリに、そして、私はスレッドAのプライベートメモリ空間にint型のコピーを保存
  2. スレッドAは新しい値0をi割り当て、更新されたiをメモリに書き戻します。

上記のプロセスは非常に単純に見えますが、並行している場合は問題が発生します。

  1. スレッドAがINT読み込み私をメインメモリに、そして、私はスレッドAのプライベートメモリ空間にint型のコピーを保存
  2. スレッドBは命令i = i + 1を実行しようとしていますが、スレッドAはまだ更新された値をメインメモリに書き戻していません
  3. スレッドBはinti = 0を読み取り、読み取った共有変数のコピーを独自のプライベートメモリスペースに書き込みます
  4. スレッドAはi = 1の割り当てを完了し、結果をメインメモリに書き戻します
  5. スレッドBは、この時点で独自のプライベートメモリ変数i = 0を取得して、i = i + 1を計算し、結果1を取得します。
  6. スレッドBは結果をメインメモリに書き戻します

期待する操作の結果はi = 2になるはずですが、スレッドBはスレッドAの更新された変数値を時間内に読み取ることができなかったため、i = 1を読み取る結果になりました。これは間違いなくスレッドセーフではありません。したがって、Javaのこの問題を解決するために、設計者はいくつかの解決策も提案しました。

第二に、逐次一貫性モデル

1.逐次一貫性モデルとは何ですか

スレッド間で同期関係が適切に処理されない場合、データの競合が発生します。Javaメモリモデルでは、データの競合を次のように定義しています。


スレッドAで変数書き込み、スレッドBで同じ変数
を読み取ります。書き込みと読み取りの間に同期はありません。

そして明らかに、データの競合が発生した後、コードは期待した結果を十分に得られないため、データの競合が発生しないようにする手段が必要です。JMMは、この時点で、スレッド間に正しい同期関係がある場合、プログラムの実行は逐次一貫性(Sequentially Consistent)を持つことを提案します。つまり、プログラムの期待される実行結果は、逐次一貫性メモリモデルの下でのプログラム。同じ。ただし、逐次整合性モデルは、コンピューターサイエンティストによって理想的に提示された参照モデルにすぎないことに注意してください。プログラマーに強力なメモリ可視性の保証を提供しますが、次の特性が達成されるため、Javaではまだ完全には実装されていません。

  1. スレッド内のすべての操作は、プログラム順に実行する必要があります
  2. すべてのスレッドは単一の操作の実行順序のみを見ることができます。逐次一貫性モデルでは、すべての操作はアトミックであり、実行直後にすべてのスレッドに表示される必要があります。

上記の2つの機能は、間違いなくJavaの他の設計機能と競合しています。たとえば、JavaではCPUが命令を並べ替えることができます。これによりプログラムの効率は向上しますが、逐次整合性モデルの要件に明らかに違反します。そのため、設計者は、Java設計で逐次一貫性モデルを満たせない場所を解決するための新しい仕様を提案しました。

2.as-if-serial

シリアルのように、シリアルのように直接見ることができますそして明らかに、as-if-serialでは、JVMがコードをどのように並べ替えても、その実行の結果はスレッドのシリアル化の結果と同じである必要があります。簡単に理解すると、並べ替えがどのように行われたとしても、シングルスレッド操作の結果を変更することはできません。これは素晴らしい提案です。Javaは、Javaが設定したルールにも厳密に準拠しています。単一のスレッドでは、コードの並べ替えが発生しても、データの可視性を常に維持できるため、次のように思われます。いくつかの幻覚、私たちは再注文の干渉を気にする必要はありませんか?次に、volatileの前に使用されたデモを見てみましょう。

public class VolatileOrderliness {
    
    

	private static long a = 0;
	private static long b = 0;
	private static long c = 0;
	private static long d = 0;
	private static long e = 0;
	private static long f = 0;
	private static long g = 0;
	private static long h = 0;

	public static long count = 0;

	public static void main(String[] args) {
    
    
		// 由于cpu指令重排发生存在概率 所以使用死循环调用 然后再出现的时候通过break跳出循环
		for (;;) {
    
    
			a = 0;
			b = 0;
			c = 0;
			d = 0;
			e = 0;
			f = 0;
			g = 0;
			h = 0;
			count++;
			Thread t1 = new Thread(() -> {
    
    
				a = 1;
				c = 101;
				d = 102;
				g = b;
			});
			Thread t2 = new Thread(() -> {
    
    
				b = 1;
				e = 201;
				f = 202;
				h = a;
			});
			t1.start();
			t2.start();
			try {
    
    
				t1.join();
				t2.join();
				String result = "count = " + count + " g = " + g + ", h=" + h;
				if (g == 0 && h == 0) {
    
    
					// 当g 和h 都出现0的时候 一定是发生了指令重排序
					System.err.println(result);
					break;
				} else {
    
    
					System.out.println(result);
				}
			} catch (InterruptedException e1) {
    
    
				e1.printStackTrace();
			}

		}

	}

}

操作結果:
ここに画像の説明を挿入
ここでは、2つのスレッドを介して共有変数を操作した後、JVMが並列状態でのデータの可視性を保証しないことは明らかです。この点で、JavaはJDK1.5の後に新しい概念を採用します。

3.発生-前

「Java並行プログラミングの技術」の第3.7.1章の例を通して理解しましょう。

double pi = 3.14 ;  // 代码A 定义pi的值为3.14
double r = 1.0 ;    //代码B定义圆的半径为1.0
double area = pi*r * r ;//代码C 计算圆的面积

発生前の原則に従うと、次のように理解できます。

  • Aが起こる-Bの前
  • Bが発生する-Cの前

上記の効果を実現するために、JMMは次のオペレーティングモデルを設計しました。
ここに画像の説明を挿入
ここで、メモリモデルは、プロセッサが非常に小さい条件下で、プログラマのメモリの強力な可視性を満たしていることがわかります。本質的に、起こる前は-Ifのように正しいです。 -シリアルセマンティックサプリメント。
「JSR-133:Javaメモリモデルとスレッド仕様」によると、発生前のルールが詳細に定義されています。

  1. プログラムシーケンスルール:各操作は、スレッド内で発生し、スレッド内で後続の操作が発生します
  2. ロックルールの監視:ロックのロック解除が発生します-ロックの前にロックします
  3. 揮発性変数のルール:揮発性ドメインへの書き込み操作が発生します-この揮発性ドメインへの読み取り操作のと後続
  4. 推移性:Aが発生する-Bの前に、Bが発生する-Cの前に、次にAが発生する-Cの前に

ここで、発生前はAが発生することを意味し、BはAがBの前に実行する必要があることを意味しますが、プログラムAの結果はプログラムBに表示されると考えてはなりません。

三、追記

この時点で、Javaメモリモデルに関連する知識ポイントは基本的に要約されています。この章の内容は理論的で理解しにくいです。私が言ったことに不明確または問題があると感じた場合は、メッセージを残してください。コメント欄、初回返信します。
幸運を!

おすすめ

転載: blog.csdn.net/xiaoai1994/article/details/111310870