1: JVMとは何か 考えてみて下さい、JVM とは何でしょうか? JVM は何に使用されますか?ここで 3 つの概念を挙げます。1 つ目は JVM、2 つ目は JDK、3 つ目は JRE です。これら 3 つは皆さんもよくご存知だと思いますし、使用したことがあると思いますが、これら 3 つの概念を明確に理解していますか?あなたがそれらを知っているかどうかは分かりません。次に、JVM についての私の理解をお見せしましょう。 (1)JVM JVM は Java Virtual Machine の略です。 JVM はコンピューティング デバイスの仕様です。実際のコンピュータ上でさまざまなコンピュータ機能をシミュレートして実装された架空のコンピュータです。 Java 言語仮想マシンが導入された後は、異なるプラットフォームで実行するときに Java 言語を再コンパイルする必要がなくなりました。 Java 言語は、Java 仮想マシンを使用して、特定のプラットフォームに関連する情報を保護します。 Java 言語コンパイラは、Java 仮想マシン上で実行されるターゲット コード (バイトコード) を生成するだけで済みます。 変更なしで複数のプラットフォームで実行できます。 Java 仮想マシンがバイトコードを実行すると、特定のプラットフォーム上でバイトコードをマシン命令に解釈して実行します。 これが、Java が「一度コンパイルすればどこでも実行できる」理由です。 (2)JDK JDK (Java Development Kit) は、Java 言語用のソフトウェア開発キット (SDK) です。 JDK の基本コンポーネントは次のとおりです。
(3)JRE JRE (Java Runtime Environment) は、JVM 標準実装や Java コア クラス ライブラリなど、JAVA プログラムを実行するために必要な環境のコレクションです。 それは 2 つの部分から構成されます: Java ランタイム環境:
Java プラグイン。 Sun の Java Runtime Environment (JRE) を使用して、Java アプレットと JavaBean コンポーネントをブラウザーで実行できるようにします。 デフォルトの Java ランタイム環境を使用してブラウザで実行する代わりに。 Java プラグインは、Netscape Navigator および Microsoft Internet Explorer で利用できます。 2: JVMランタイムデータ領域 Java プログラムを実行するとき、Java 仮想マシンは管理するメモリを複数の異なるデータ領域に分割します。これらのエリアには独自の用途があり、作成時間と破棄時間があります。一部の領域は仮想マシン プロセスの開始時に作成されますが、一部の領域はユーザー スレッドの開始と終了に応じて作成および破棄されます。 Java 仮想マシン仕様 (Java SE 7) によれば、Java 仮想マシンによって管理されるメモリには、次の図に示すように、次のランタイム データ領域が含まれます。 2.1 プログラムカウンタ プログラム カウンター レジスタは、現在のスレッドによって実行されたバイトコードの行番号インジケータと見なすことができる、小さなメモリ空間です。仮想マシンの概念モデル (これはあくまでも概念モデルであり、さまざまな仮想マシンがより効率的な方法で実装される可能性があります) では、バイトコード インタープリターはこのカウンターの値を変更して、次に実行するバイトコード命令を選択することで動作します。分岐、ループ、ジャンプ、例外処理、スレッド回復、その他の基本機能はすべて、このカウンターに依存して完了します。 Java 仮想マシンのマルチスレッド化は、スレッドを順番に切り替えてプロセッサ実行時間を割り当てることによって実現されます。プロセッサは、ある瞬間に 1 つのスレッド内の命令のみを実行します。そのため、スレッド切り替え後に正しい実行位置に戻すためには、各スレッドが独立したプログラム カウンターを持つ必要があります。各スレッドのカウンターは互いに影響を及ぼさず、独立して保存されます。 スレッドが Java メソッドを実行している場合、カウンターは実行中のバイトコード命令のアドレスを記録します。ネイティブ メソッドが実行されている場合、カウンター値は空 (未定義) になります。 このメモリ領域は、Java 仮想マシン仕様で OutOfMemoryError 条件が指定されていない唯一の領域です。 プログラム カウンターはスレッド専用であり、そのライフ サイクルはスレッドと同じです (スレッドとともに生成され、スレッドとともに破棄されます)。 2.2 Java仮想マシンスタック Java 仮想マシン スタックは、Java メソッド実行のメモリ モデルを記述します。メソッドが実行されるたびに、ローカル変数テーブル、オペランド スタック、動的リンク、メソッド終了などの情報を格納するためのスタック フレームが作成されます。メソッドが呼び出されてから実行されるまでのプロセスは、スタック フレームが仮想マシン スタックにプッシュされてからスタックからポップされるまでのプロセスに対応します。 Java 仮想マシン仕様では、この領域に対して 2 つの例外が定義されています。 スレッドによって要求されたスタックの深さが仮想マシンで許可されている深さよりも大きい場合、StackOverflowError 例外がスローされます。 仮想マシン スタックを動的に拡張できる場合 (現在の Java 仮想マシンのほとんどは拡張可能)、拡張中に十分なメモリを要求できない場合は、OutOfMemoryError 例外がスローされます。 プログラム レジスタと同様に、Java 仮想マシン スタックもスレッド専用であり、そのライフ サイクルはスレッドと同じです。 2.3.ローカルメソッドスタック ネイティブ メソッド スタックが果たす役割は、仮想マシン スタックの役割と非常に似ています。それらの違いは、仮想マシン スタックは仮想マシンによる Java メソッドの実行を提供するのに対し、ネイティブ メソッド スタックは仮想マシンによって使用されるネイティブ メソッドを提供するという点です。仮想マシン仕様では、ローカル メソッド スタック内のメソッドで使用される言語、使用法、およびデータ構造に必須の要件が課されていないため、特定の仮想マシンで自由に実装できます。 仮想マシン スタックと同様に、ローカル メソッド スタック領域でも StackOverflowError および OutOfMemoryError 例外がスローされる可能性があります。 仮想マシン スタックと同様に、ネイティブ メソッド スタックもスレッドプライベートです。 2.4 Javaヒープ ほとんどのアプリケーションでは、Java ヒープは Java 仮想マシンによって管理されるメモリの最大の部分です。 Java ヒープはすべてのスレッドで共有されるメモリ領域であり、仮想マシンの起動時に作成されます。このメモリ領域の唯一の目的は、オブジェクト インスタンスを格納することです。ほぼすべてのオブジェクト インスタンスと配列は、ここでメモリを割り当てる必要があります。 Java ヒープはガベージ コレクターによって管理される主要な領域であるため、「GC ヒープ」(ガベージ コレクション ヒープ) と呼ばれることがよくあります。メモリ回復の観点から見ると、現在ほとんどのコレクターは世代別コレクション アルゴリズムを使用しているため、Java ヒープはさらに新しい世代と古い世代に分割できます。新しい世代はさらに、エデン空間、From Survivor 空間、To Survivor 空間に分類できます。 Java 仮想マシン仕様によれば、Java ヒープは、ディスク領域と同様に、論理的に連続している限り、物理的に不連続なメモリ領域に存在できます。実装する際は固定サイズでも拡張可能でも構いませんが、現在主流の仮想マシンはすべて拡張可能(-Xms と -Xmx で制御)に実装されています。インスタンスの割り当てを完了するためのメモリがヒープ内に存在せず、ヒープを拡張できなくなった場合は、OutOfMemoryError 例外がスローされます。 2.5.方法領域 メソッド領域は、Java ヒープと同様に、すべてのスレッドで共有されるメモリ領域です。これは、クラス情報、定数、静的変数、JIT コンパイルされたコード、および仮想マシンによってロードされたその他のデータを格納するために使用されます。メソッド領域は、仮想マシンの起動時に作成されます。 Java 仮想マシン仕様では、メソッド領域に対する制限が非常に緩やかです。ヒープのような不連続なメモリ空間を必要とせず、固定サイズにしたり拡張可能にしたりできることに加えて、ガベージ コレクションを実装しないことも選択できます。 Java 仮想マシン仕様によれば、メソッド領域のメモリ空間がメモリ割り当てのニーズを満たせない場合、OutOfMemoryError 例外がスローされます。 2.6.ランタイム定数プール ランタイム定数プールはメソッド領域の一部です。クラス ファイルには、クラス バージョン、フィールド、メソッド、インターフェイス、およびその他の説明情報に加えて、コンパイル中に生成されるさまざまなリテラルとシンボリック参照を格納するために使用される定数プール (定数プール テーブル) も含まれています。このコンテンツは、クラスがロードされた後にメソッド領域内のランタイム定数プールに保存されます。 2.7 直接記憶 ダイレクト メモリは、仮想マシンのランタイム データ領域の一部ではなく、Java 仮想マシン仕様で定義されているメモリ領域でもありません。ただし、このメモリ部分も頻繁に使用されるため、OutOfMemoryError 例外が発生する可能性もあります。 NIO (New Input/Output) クラスは JDK 1.4 で新しく追加され、チャネルとバッファに基づく I/O メソッドを導入しました。ネイティブ関数ライブラリを使用してオフヒープ メモリを直接割り当て、このメモリへの参照として Java ヒープに格納されている DirectByteBuffer オブジェクトを介して操作することができます。これにより、Java ヒープとネイティブ ヒープ間でのデータのやり取りが回避され、一部のシナリオでパフォーマンスが大幅に向上します。 この時点で、Java 仮想マシンのランタイム領域の概要がわかりました。次回も引き続き、JVM 関連の情報をお伝えしていきます。 3: JVMメモリモデル Java メモリ モデルは、Java メモリ モデル (JMM とも呼ばれます) です。 JMM は、Java 仮想マシン (JVM) がコンピューターのメモリ (RAM) 内でどのように動作するかを定義します。 JVM はコンピュータ全体の仮想モデルなので、JMM は JVM に属します。 Java 並行プログラミングをより深く理解したい場合は、まず Java メモリ モデルを理解する必要があります。 Java メモリ モデルは、複数のスレッド間での共有変数の可視性と、必要に応じて共有変数を同期する方法を定義します。オリジナルの Java メモリ モデルはあまり効率的ではなかったため、Java 1.5 でリファクタリングされ、現在の Java 8 でも Java 1.5 バージョンが引き続き使用されています。 並行プログラミングについて 並行プログラミングの分野では、スレッド間の通信と同期という 2 つの重要な問題があります。 スレッド間の通信 スレッド通信とは、スレッドが情報を交換するメカニズムを指します。命令型プログラミングでは、スレッド間に共有メモリとメッセージ パッシングという 2 つの通信メカニズムがあります。 共有メモリ同時実行モデルでは、スレッドはプログラムの共通状態を共有し、スレッドはメモリ内の共通状態の書き込みと読み取りによって暗黙的に通信します。一般的な共有メモリ通信方法は、共有オブジェクトを介して通信することです。 メッセージ パッシング同時実行モデルでは、スレッド間に共通の状態はなく、スレッドはメッセージを送信して明示的に通信する必要があります。 Java の一般的なメッセージ パッシング メソッドは wait() と notification() です。 Java スレッド間の通信の詳細については、「スレッド間の通信 (スレッド シグナル)」を参照してください。 スレッド間の同期 同期とは、プログラムが異なるスレッド間で操作が発生する相対的な順序を制御するために使用するメカニズムを指します。 共有メモリ同時実行モデルでは、同期は明示的に実行されます。プログラマーは、メソッドまたはコードセクションをスレッド間で相互に排他的に実行する必要があることを明示的に指定する必要があります。 メッセージ パッシングの同時実行モデルでは、メッセージの送信がメッセージの受信に先行する必要があるため、同期は暗黙的に行われます。 Javaの並行処理では共有メモリモデルを使用する Java スレッド間の通信は常に暗黙的に行われ、通信プロセス全体がプログラマーにとって完全に透過的です。暗黙的なスレッド間通信の仕組みを理解していないマルチスレッド プログラムを作成する Java プログラマーは、さまざまな奇妙なメモリ可視性の問題に遭遇する可能性があります。 Java メモリ モデル 前述のように、Java スレッド間の通信は共有メモリ モデルを採用しています。ここで言及されている共有メモリ モデルは、Java メモリ モデル (略して JMM) を指します。 JMM は、共有変数へのスレッドの書き込みが別のスレッドに表示されるタイミングを決定します。抽象的な観点から、JMM はスレッドとメイン メモリ間の抽象的な関係を定義します。スレッド間の共有変数はメイン メモリに格納され、各スレッドには、スレッドが読み取り/書き込みできる共有変数のコピーを格納するプライベート ローカル メモリがあります。ローカル メモリは JMM の抽象的な概念であり、実際には存在しません。キャッシュ、書き込みバッファ、レジスタ、その他のハードウェアおよびコンパイラの最適化について説明します。 上の図から、スレッド A とスレッド B が通信する場合、次の 2 つの手順を実行する必要があります。 1. まず、スレッド A はローカル メモリ A 内の更新された共有変数をメイン メモリに更新します。 2. 次に、スレッド B はメイン メモリにアクセスして、スレッド A が以前に更新した共有変数を読み取ります。 次の図は、これらの 2 つのステップを示しています。 上の図に示すように、ローカルメモリ A と B には、メインメモリ内の共有変数 x のコピーがあります。最初は、これら 3 つのメモリ内の x 値がすべて 0 であると仮定します。スレッド A が実行中の場合、更新された x 値 (値は 1 であると仮定) を自身のローカル メモリ A に一時的に保存します。スレッド A とスレッド B が通信する必要がある場合、スレッド A はまずローカル メモリ内の変更された x 値をメイン メモリに更新し、メイン メモリ内の x 値は 1 になります。続いて、スレッド B はメイン メモリにアクセスして、スレッド A によって更新された x 値を読み取ります。このとき、スレッド B のローカル メモリ内の x 値も 1 になります。 全体的な観点から見ると、これら 2 つのステップは基本的にスレッド A がスレッド B にメッセージを送信することであり、この通信プロセスはメイン メモリを通過する必要があります。 JMM は、メイン メモリと各スレッドのローカル メモリ間の相互作用を制御することにより、Java プログラマーにメモリの可視性保証を提供します。 前述のように、Java メモリ モデルは単なる抽象的な概念ですが、Java ではどのように機能するのでしょうか。 Java メモリ モデルの動作をよりよく理解するために、以下では、Java メモリ モデル、ハードウェア メモリ モデル、およびそれらの間のブリッジの JVM 実装について詳しく説明します。 JVM の Java メモリ モデルの実装 JVM 内では、Java メモリ モデルによってメモリがスレッド スタック領域とヒープ領域の 2 つの部分に分割されます。次の図は、JVM 内の Java メモリ モデルの論理ビューを示しています。 JVM で実行されている各スレッドには独自のスレッド スタックがあり、その中に現在のスレッドによって実行されたメソッド呼び出しに関する情報が含まれています。これをコールスタックとも呼びます。コードの実行が続くと、コールスタックは変化し続けます。 スレッド スタックには、現在のメソッドのすべてのローカル変数情報も含まれます。スレッドは自身のスレッド スタックのみを読み取ることができます。つまり、スレッド内のローカル変数は他のスレッドからは見えません。 2 つのスレッドが同じコードを実行している場合でも、それぞれのスレッド スタックにローカル変数が作成されるため、各スレッドには独自のバージョンのローカル変数が存在します。 プリミティブ型 (boolean、byte、short、char、int、long、float、double) のすべてのローカル変数はスレッド スタックに直接格納され、その値はスレッド間で独立しています。プリミティブ型のローカル変数の場合、あるスレッドは別のスレッドにコピーを渡すことはできますが、それらのスレッド間で共有することはできません。 ヒープ領域には、どのスレッドがオブジェクトを作成したかに関係なく、Java アプリケーションによって作成されたすべてのオブジェクト情報が含まれます。オブジェクトには、プリミティブ型のカプセル化されたクラス (Byte、Integer、Long など) が含まれます。オブジェクトがメンバー変数に属しているか、メソッド内のローカル変数に属しているかに関係なく、ヒープ領域に格納されます。 次の図は、呼び出しスタックとローカル変数がスタック領域に格納され、オブジェクトがヒープ領域に格納されていることを示しています。 ローカル変数がプリミティブ型の場合、その変数はスタック領域に完全に格納されます。 ローカル変数はオブジェクトへの参照である場合もあります。その場合、ローカル参照はスタックに格納されますが、オブジェクト自体はヒープに格納されます。 オブジェクトのメンバー メソッドの場合、これらのメソッドにはローカル変数が含まれており、それが属するオブジェクトがヒープ領域にある場合でも、ローカル変数はスタック領域に格納する必要があります。 オブジェクトのメンバー変数は、プリミティブ型であってもラッパー型であっても、ヒープ領域に格納されます。 静的型変数とクラス関連の情報は、クラス自体とともにヒープ内に格納されます。 ヒープ内のオブジェクトは複数のスレッドで共有できます。スレッドがオブジェクトへの参照を取得すると、そのオブジェクトのメンバー変数にアクセスできます。 2 つのスレッドが同じオブジェクトの同じメソッドを同時に呼び出すと、2 つのスレッドは同時にオブジェクトのメンバー変数にアクセスできますが、ローカル変数の場合は、各スレッドが独自のスレッド スタックにコピーをコピーします。 次の図は、上記のプロセスを示しています。 ハードウェアメモリアーキテクチャ どのようなメモリ モデルであっても、最終的にはコンピューターのハードウェア上で実行されるため、コンピューターのハードウェア メモリ アーキテクチャを理解する必要があります。次の図は、現代のコンピューター ハードウェア メモリ アーキテクチャを簡単に説明したものです。 現代のコンピュータには通常 2 つ以上の CPU が搭載されており、各 CPU には複数のコアが含まれている場合があります。したがって、アプリケーションがマルチスレッドの場合、これらのスレッドは各 CPU コア上で並列に実行される可能性があります。 CPU 内には CPU のストレージである一連の CPU レジスタがあります。 CPU は、コンピューターのメインメモリよりもはるかに高速にレジスタを操作できます。メインメモリと CPU レジスタの間にも CPU キャッシュがあります。 CPU は、CPU キャッシュをメイン メモリよりも高速に操作しますが、CPU レジスタよりも低速です。一部の CPU には複数のキャッシュ レイヤー (L1 キャッシュと L2 キャッシュ) がある場合があります。コンピュータのメインメモリは RAM とも呼ばれます。すべての CPU はメイン メモリにアクセスでき、メイン メモリは前述のキャッシュやレジスタよりもはるかに大きくなります。 CPU がメイン メモリにアクセスする必要がある場合、最初にメイン メモリ データの一部を CPU キャッシュに読み込み、次に CPU キャッシュをレジスタに読み込みます。 CPU がメイン メモリにデータを書き込む必要がある場合、最初にレジスタを CPU キャッシュにフラッシュし、次に一部のノードでキャッシュ データをメイン メモリにフラッシュします。 Javaメモリモデルとハードウェアアーキテクチャのギャップを埋める 前述のように、Java メモリ モデルはハードウェア メモリ アーキテクチャと一致していません。ハードウェア メモリ アーキテクチャでは、スタックとヒープが区別されません。ハードウェアの観点から見ると、スタックとヒープの両方のデータのほとんどはメインメモリに格納されます。もちろん、スタックとヒープ内のデータの一部は CPU レジスタに格納されることもあります。次の図に示すように、Java メモリ モデルとコンピュータ ハードウェア メモリ アーキテクチャは交差関係にあります。 オブジェクトや変数がコンピュータのさまざまなメモリ領域に保存されると、必然的にいくつかの問題が発生します。最も重要な 2 つの問題は次のとおりです。 1. 各スレッドへの共有オブジェクトの可視性 2. 共有オブジェクトをめぐる競争123 共有オブジェクトの可視性 複数のスレッドが同時に同じ共有オブジェクトを操作する場合、 volatile キーワードと synchronization キーワードが適切に使用されていないと、あるスレッドによる共有オブジェクトへの更新が他のスレッドには見えなくなる可能性があります。 共有オブジェクトがメインメモリに格納されていると想像してください。 1 つの CPU 内のスレッドがメイン メモリ データを CPU キャッシュに読み込み、共有オブジェクトに変更を加えます。ただし、CPU キャッシュ内の変更されたオブジェクトはメイン メモリにフラッシュされていません。この時点では、スレッドによる共有オブジェクトへの変更は、他の CPU のスレッドには表示されません。最終的に、各スレッドは共有オブジェクトをコピーし、コピーされたオブジェクトは異なる CPU キャッシュに配置されます。 次の図は、上記のプロセスを示しています。左側の CPU で実行されているスレッドは、共有オブジェクト obj をメイン メモリから CPU キャッシュにコピーし、オブジェクト obj の count 変数を 2 に変更します。ただし、この変更はメイン メモリにフラッシュされていないため、右側の CPU で実行されているスレッドには表示されません。 共有オブジェクトの可視性の問題を解決するには、Java の volatile キーワードを使用できます。 Java の volatile キーワード。 volatile キーワードにより、変数がメイン メモリから直接読み取られ、変数の更新がメイン メモリに直接書き込まれるようになります。揮発性原理は、後述する CPU メモリ バリア命令に基づいて実装されます。 競争 複数のスレッドがオブジェクトを共有し、共有オブジェクトを同時に変更すると、競合状態が発生します。 下の図に示すように、スレッド A とスレッド B はオブジェクト obj を共有します。スレッド A がメインメモリから Obj.count 変数を自身の CPU キャッシュに読み取るとします。同時に、スレッド B も Obj.count 変数を CPU キャッシュに読み取り、両方のスレッドが Obj.count に 1 を加算します。この時点で、Obj.count に 1 を加算する操作は 2 回実行されますが、両方とも異なる CPU キャッシュで実行されます。 これら 2 つの加算操作を連続して実行すると、Obj.count 変数は元の値に 2 を加算し、メイン メモリ内の Obj.count の最終値は 3 になります。ただし、下の図の 2 つの加算操作は並列です。スレッド A とスレッド B のどちらが先に計算結果をメイン メモリにフラッシュしたとしても、合計 2 回の加算操作があったとしても、メイン メモリ内の Obj.count は 1 ~ 2 しか増加しません。 上記の問題を解決するには、Java 同期コード ブロックを使用できます。同期コード ブロックにより、同時に 1 つのスレッドだけがコード競合領域に入ることができるようになります。同期されたコード ブロックでは、コード ブロック内のすべての変数がメイン メモリから読み取られることも保証されます。スレッドがコード ブロックを終了すると、変数が volatile 型であるかどうかに関係なく、すべての変数の更新がメイン メモリにフラッシュされます。 揮発性と同期性の違い
Javaメモリモデルの基礎となる基本原則 命令の並べ替え プログラムを実行する際、コンパイラとプロセッサはパフォーマンスを向上させるために命令の順序を変更します。ただし、JMM は、特定の種類のメモリ バリアを挿入することで、異なるコンパイラおよび異なるプロセッサ プラットフォーム上で特定の種類のコンパイラ並べ替えおよびプロセッサ並べ替えが禁止されるようにし、上位層に対して一貫したメモリ可視性を保証します。
データ依存性 2 つの操作が同じ変数にアクセスし、そのうちの 1 つが書き込み操作である場合、2 つの操作の間にはデータ依存関係が存在します。 コンパイラとプロセッサは、データ依存関係を持つ 2 つの操作の実行順序を変更しません。つまり、順序を変更しません。 あたかもシリアル 並べ替えがどのように行われたとしても、単一スレッドでの実行結果は変更できず、コンパイラ、ランタイム、およびプロセッサは as-if-serial セマンティクスに準拠する必要があります。 記憶の壁 前述のように、メモリ バリアは特定の種類のプロセッサの並べ替えを禁止し、プログラムが期待どおりのプロセスに従って実行されるようにします。メモリ バリア (メモリ フェンスとも呼ばれる) は、CPU 命令です。基本的には次のような指示です。
コンパイラと CPU は、同じ最終結果を保証するために命令の順序を変更し、パフォーマンスを最適化しようとします。メモリ バリアを挿入すると、このメモリ バリア命令では命令の順序を変更できないことがコンパイラと CPU に通知されます。 メモリバリアが行うもう一つのことは、さまざまな CPU キャッシュのフラッシュを強制することです。たとえば、書き込みバリアは、バリアの前にキャッシュに書き込まれたすべてのデータをフラッシュするため、どの CPU 上のどのスレッドでもこのデータの最新バージョンを読み取ることができます。 これは Java とどのような関係があるのでしょうか?上記の Java メモリ モデルで言及されている volatile は、メモリ バリアに基づいて実装されています。 変数が揮発性の場合、JMM はこのフィールドに書き込んだ後に Write-Barrier 命令を挿入し、このフィールドを読み取る前に Read-Barrier 命令を挿入します。つまり、揮発性変数に書き込むと、次のことが保証されます。
事前に起こる JDK5 以降、Java は新しい JSR-133 メモリ モデルを使用します。このモデルは、事前発生の概念に基づいて操作間のメモリの可視性を説明します。 JMM では、ある操作の実行結果を別の操作から参照できるようにする必要がある場合、2 つの操作の間に事前発生関係が存在する必要があります。 2 つの操作は同じスレッド内または 2 つの異なるスレッド内で実行できます。 プログラマーに最も関連のある事前発生ルールは次のとおりです。
注意: 2 つの操作の間に事前発生関係が存在するということは、前の操作が後者の操作の前に実行されなければならないことを意味するものではありません。必要なのは、前の操作の実行結果が後の操作から見えることと、前の操作が後の操作の前に順序付けられていることだけです。 |
<<: 2020年ガートナー社グローバルMSPマジッククアドラント
>>: Python で Apache Kafka を使いこなすために知っておくべき 3 つのライブラリ
ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービス今日のインターネット時代...
西洋の「バレンタインデー」は、長年輸入され、現在では深くローカライズされており、電子商取引会社のター...
5月28日、北京で2021 Alibaba Cloud Summitが開催されました。 Alibab...
VLANとは何ですか? VLAN (Virtual LAN) は、中国語に翻訳すると「仮想ローカルエ...
第3世代の技術を持つと主張する360 Searchは、ウェブマスターに何をもたらすのでしょうか?権威...
クラウド セキュリティは現在、あらゆる主要なデジタル変革の取り組みにおいて重要な役割を果たしており、...
マイクロサービスの分割後に発生する問題の 1 つは、分散後の一貫性の問題です。モノリシック アーキテ...
ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービス外部リンクの最適化につい...
本紙(記者:簡公波、インターン:曹志光)は昨日、市公安局の「インターネット社会信用ネットワーク」協力...
以前、質疑応答形式をベースとした業界フォーラムは主に技術者を対象としていると述べました。同様に、業界...
熟練したウェブマスターのほとんどが、新しいサイトの最適化の経験を持っています。新しいサイトの最適化は...
SEO 技術を勉強すると、誰もがこのような感覚を抱くでしょう。つまり、ウェブサイトのキーワードを上位...
多くの企業が、普遍的なエラー訂正量子コンピュータの夢を一歩ずつ実現するために、大胆かつ興味深いステッ...
はじめにと概要HPA に関する最初の 2 つの記事では、HPA の実装原理について学び、サービス C...
モバイルインターネットの発展により、従来の金融業界にまったく新しいビジネスモデルが生まれました。金融...