「JVM仮想マシンの詳細な理解」学習日記第12章Javaメモリモデルとスレッド

概要

コンピュータプロセッサの計算速度は、ストレージおよびサブ通信システムの速度とはかけ離れているため、プロセッサはほとんどの時間をアイドル待機状態で費やします。プロセッサのリソースを無駄にしたくない場合は、プロセッサをスクイーズする方法を見つける必要があります。マルチタスクは、「圧迫」の非常に効果的な手段と見なされています。

1.ハードウェアの効率と一貫性

プロセッサとメモリ間の計算速度は数桁異なるため、最近のコンピュータでは、キャッシュの1つまたは複数の層を追加する必要があります。キャッシュの読み取りおよび書き込み速度は、プロセッサとメモリ間のインターフェイスとして、プロセッサの計算速度に近づく可能性があります。 。バッファ間:操作に必要なデータをメモリからキャッシュにコピーして、操作をすばやく実行できるようにします。操作が完了した後、キャッシュからメモリに同期して、プロセッサが実行します。遅いメモリの読み取りと書き込みを待つ必要はありません。

マルチプロセッサシステムでは、この構造によってキャッシュコヒーレンスという新しい問題も発生します。コンピュータには複数のプロセッサがあり、それぞれに独自のキャッシュがあり、同じメインメモリを共有しています。複数のプロセッサのコンピューティングタスクに同じメインメモリ領域が含まれる場合、それぞれのキャッシュデータに一貫性がない可能性があります。したがって、各プロセッサのキャッシュデータの一貫性を確保するために、対応するキャッシュコヒーレンスプロトコルを策定する必要があります。

image.png

キャッシュを増やすことに加えて、プロセッサ内の演算装置を最大限に活用するために、プロセッサは入力コードに対してアウトオブオーダー実行の最適化を実行する場合があり、エグゼキュータはアウトオブオーダーの結果を再編成します。結果が同じであることを確認するための実行順次実行の結果は同じですが、シーケンスは必ずしもコードのシーケンスと同じである必要はありません。

2.Javaメモリモデル

これは、さまざまなハードウェアとオペレーティングシステムの違いを保護するために使用されるため、Javaプログラムはさまざまなプラットフォームで一貫したメモリアクセス効果を実現できます。

Javaメモリモデルでは、各スレッドに独自の作業メモリ(プロセッサキャッシュと同様)があることに加えて、すべての変数がメインメモリ(ハードウェアメモリではなく仮想マシンメモリの一部)に格納されることが規定されています。スレッドの作業メモリーは、スレッドが使用する変数のメインメモリーのコピーを保存します。変数に対するスレッドのすべての操作は、作業メモリーで実行する必要があり、メインメモリー内のデータを直接読み書きすることはできません。異なるスレッド間に直接接続はありません。他のスレッドの変数にアクセスするには、スレッド間の変数値の送信をメインメモリを介して完了する必要があります。スレッド、メインメモリ、および作業メモリ間の関係は次のとおりです。図に示されています。

image.png

3.メモリ間の相互作用

メインメモリからワーキングメモリに変数をコピーする方法、およびワーキングメモリからメインメモリに同期する方法の実装の詳細に関して、Javaメモリモデルで完了するために次の8つの操作が定義されています。

lock: 作用于主内存变量,把一个变量标识为一条线程独占的状态。
unlock:作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read:作用于主内存的变量,它把一个变量的值从主内存中传输到线程的工作内存中,以便随后的load动作使用。
load:作用于工作内存的变量,把read操作从主内存中得到的变量值放入到工作内存的变量副本中
use:作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用给变量赋值的字节码指令时执行这个操作。
assign:作用于工作内存变量,把从执行引擎中接收的值赋值给工作内存变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store:作用于工作内存变量,把工作内存中变量的值传送到主内存中,以便随后的write操作使用。
write:作用于主内存中的变量,把store操作从工作内存中得到的变量值放入到主内存变量中。 
复制代码

揮発性変数の特別なルール

  1. 共有変数の可視性が保証されます。

可視性とは、スレッドがこの変数の値を変更すると、新しい値が他のスレッドにすぐに表示されることを意味します。ただし、通常の変数ではこれを行うことはできません。通常の変数の値がスレッド間で渡される場合は、メインメモリを介して完了する必要があります。

  1. 命令の並べ替えを無効にする

volatileキーワードを持つ変数をJavaバイトコードにコンパイルすると、変更時にロック操作が追加されます。これはメモリバリアに相当します。

並行性の3つの特徴

原子性

一定期間内に同時に実行できるスレッドは1つだけで、他のスレッドは実行できません。つまり、「ロック」後のコードブロックです。

視認性

スレッドが共有変数の値を変更すると、他のスレッドはその変更をすぐに認識します。

秩序

Javaプログラムの自然順序付けは、1つの文にまとめることができます。このスレッドで観察された場合、すべての操作が順序付けられ、あるスレッドが別のスレッドで観察された場合、すべての操作が順序どおりになりません。プロセッサが操作結果が正しいことを確認するときに発生する命令の並べ替えの現象は、以前に導入されました。

原則の前に起こる

  • プログラムの順序規則

スレッド内では、先に記述された操作は、後で記述された操作の前に、制御フローの順序で発生します。

  • ロックルールを監視する

ロック解除操作は、同じロックに対する後続のロック操作の前に最初に発生します。

  • 揮発性変数のルール

揮発性変数への書き込みは、その後の変数の読み取りの前に発生します。

  • スレッド開始ルール

Thread.start()メソッドは、このスレッドで発生するすべてのアクションの前にあります。

  • スレッド終了ルール

スレッド内のすべての操作は、このスレッドの終了検出の前に発生します。

  • 推移性

如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

Java语言无须使用任何同步手段保障就能够成立的先行发生规则有且只有上面这些。

线程的实现方式

内核线程实现

内核线程是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上,每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情。

程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口——轻量级进程,由于每一个轻量级 进程都由一个内核线程支持,因此只有先支持内核线程才能有轻量级进程。

用户线程实现

广义上来讲,一个线程只要不是内核线程,都可以认为是用户线程。狭义上用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知到用户线程的存在及如何实现的。用户线程的建立,同步,销毁和调度完全在用户态中完成,不需要内核的帮助。

混合实现(Java线程实现方式)

线程除了依赖内核线程实现和完全由用户线程自己实现之外,还有一种将内核线程和用户线程一起使用的方式。这种混合实现的方式下,既有用户线程,又有内核线程。用户线程还是完全建立在用户空间中,因此用户线程的创建,切换等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统支持的轻量级进程则可以作为用户线程和内核线程之间的桥梁,降低整个进程被阻塞的风险。

内核线程的调度成本为什么高?

其成本主要来自用户态和内核态之间的状态转换,而这两种状态转换的开销主要来自于响应中断、保护和恢复执行现场的成本。

总结

  1. 处理器进行运算时不是直接和内存进行交互处理数据的,而是在处理器的高速缓存中进行运算处理,处理完成后再同步回内存。
  2. Javaメモリモデルは、実際には存在しない一連の抽象的な概念であり、プログラム内の各変数にアクセスする方法を説明します。メインメモリはメソッド領域とヒープ領域に対応し、ワーキングメモリはJava仮想マシンスタックとネイティブメソッドスタックです。
  3. volatileの2つの主要な機能:命令の並べ替えの禁止と共有変数の可視性の確保
  4. スレッドを実装する最後の3つの方法であるJavaスレッドは、カーネルスレッド+ユーザースレッドの方法を使用して実装されます。
  5. Javaプログラムの発生前の原則。
  6. ユーザーモードからカーネルモードに切り替えるとパフォーマンスに影響するのはなぜですか?

おすすめ

転載: juejin.im/post/7079384045334986782