Javaでのスレッドモデル
1. 序章
多くの場合、私たちのアプリケーションでは、同時に複数のことを実行できる必要があります。 これはいくつかの方法で実現できますが、その中で重要なのは、何らかの形でマルチタスクを実装することです。
マルチタスクとは、複数のタスクを同時に実行することを意味します。各タスクはその作業を実行します。 これらのタスクは通常、すべて同時に実行され、同じメモリの読み取りと書き込み、同じリソースとの対話を行いますが、異なることを行います。
2. ネイティブスレッド
Javaでマルチタスクを実装する標準的な方法は、スレッドを使用することです。 スレッド化は通常、オペレーティングシステムまでサポートされています。 このレベルで機能するスレッドを「ネイティブスレッド」と呼びます。
オペレーティングシステムには、基盤となるハードウェアにどれだけ近いかという理由だけで、アプリケーションでは利用できないことが多いスレッド化機能がいくつかあります。 これは、ネイティブスレッドの実行が通常より効率的であることを意味します。 これらのスレッドは、コンピューターのCPU上の実行スレッドに直接マップされ、オペレーティングシステムはCPUコアへのスレッドのマッピングを管理します。
すべてのJVM言語をカバーするJavaの標準スレッドモデルは、ネイティブスレッドを使用します。 これはJava1.2以降に当てはまり、JVMが実行されている基盤となるシステムに関係なく当てはまります。
これは、Javaの標準のスレッドメカニズムのいずれかを使用するときはいつでも、ネイティブスレッドを使用していることを意味します。 これには、 java.lang.Thread 、 java.util.concurrent.Executor 、java.util.concurrent.ExecutorServiceなどが含まれます。
3. グリーンスレッド
通常、これは複数のネイティブスレッドを実行し、実行のためにこれらのネイティブスレッドにグリーンスレッドを割り当てることで機能します。 次に、システムは、任意の時点でアクティブになっているグリーンスレッドと、それらがアクティブになっているネイティブスレッドを選択できます。
これは非常に複雑に聞こえますが、そうです。 しかし、それは私たちが一般的に気にする必要のない合併症です。 基盤となるアーキテクチャがこれらすべてを処理し、ネイティブのスレッドモデルであるかのように使用できるようになります。
では、なぜこれを行うのでしょうか? ネイティブスレッドは非常に効率的に実行できますが、開始と停止に高いコストがかかります。 グリーンスレッドは、このコストを回避し、アーキテクチャにさらに柔軟性を与えるのに役立ちます。 比較的長時間実行されるスレッドを使用している場合、ネイティブスレッドは非常に効率的です。 非常に短期間のジョブの場合、それらを開始するコストは、それらを使用する利点を上回る可能性があります。 このような場合、グリーンスレッドはより効率的になる可能性があります。
残念ながら、Javaにはグリーンスレッドのサポートが組み込まれていません。
非常に初期のバージョンでは、標準のスレッドモデルとしてネイティブスレッドの代わりにグリーンスレッドが使用されていました。 これはJava1.2で変更され、それ以降、JVMレベルでのサポートはありません。
また、ライブラリにグリーンスレッドを実装することも困難です。これは、グリーンスレッドを適切に実行するには、非常に低レベルのサポートが必要になるためです。 そのため、使用される一般的な代替手段はファイバーです。
4. 繊維
ファイバーはマルチスレッドの代替形式であり、グリーンスレッドに似ています。 どちらの場合も、ネイティブスレッドを使用せず、代わりに、いつでも実行されている基盤となるシステムコントロールを使用しています。 グリーンスレッドとファイバーの大きな違いは、コントロールのレベル、特に誰がコントロールしているのかということです。
グリーンスレッドは、プリエンプティブマルチタスクの一種です。 これは、基盤となるアーキテクチャが、任意の時点で実行されているスレッドを決定する責任を完全に負うことを意味します。
これは、スレッドの通常の問題がすべて当てはまるということを意味します。スレッドの実行順序や、同時に実行されるスレッドについては何もわかりません。 また、基盤となるシステムは、メソッドやステートメントの途中で、いつでもコードを一時停止および再起動できる必要があることも意味します。
ファイバーは、代わりに協調マルチタスクの形式です。つまり、実行中のスレッドは、別のに譲ることができることを通知するまで実行を続けます。 それは、繊維が互いに協力することは私たちの責任であることを意味します。 これにより、システムがこれを決定するのではなく、ファイバーが実行を一時停止できるタイミングを直接制御できます。
これは、これを可能にする方法でコードを記述する必要があることも意味します。 そうしないと、機能しません。 コードに中断ポイントがない場合は、ファイバーをまったく使用していない可能性があります。
Javaには現在、ファイバーのサポートが組み込まれていません。 これをアプリケーションに導入できるライブラリがいくつかあります。これには、次のものが含まれますが、これらに限定されません。
4.1. クエーサー
Quasar は、純粋なJavaおよびKotlinで適切に機能するJavaライブラリであり、Clojureで機能する代替バージョンがあります。
これは、アプリケーションと一緒に実行する必要のあるJavaエージェントを持つことで機能します。このエージェントは、ファイバーを管理し、それらが正しく連携することを保証する責任があります。 Javaエージェントを使用するということは、特別なビルド手順が必要ないことを意味します。
Quasarでは、Java 11が正しく機能する必要があるため、Java11を使用できるアプリケーションが制限される可能性があります。 古いバージョンはJava8で使用できますが、これらは積極的にサポートされていません。
4.2. キリム
Kilimは、Quasarと非常によく似た機能を提供するJavaライブラリですが、Javaエージェントの代わりにバイトコードウィービングを使用して提供します。 これは、より多くの場所で機能できることを意味しますが、ビルドプロセスがより複雑になります。
KilimはJava7以降で動作し、Javaエージェントがオプションではないシナリオでも正しく動作します。 たとえば、別のものがすでに計測または監視に使用されている場合です。
4.3. プロジェクトルーム
Project Loomは、アドオンライブラリとしてではなく、JVM自体にファイバーを追加するためのOpenJDKプロジェクトによる実験です。 これにより、糸よりも繊維の利点が得られます。 これをJVMに直接実装することで、Javaエージェントとバイトコードウィービングがもたらす複雑さを回避するのに役立ちます。
Project Loomの現在のリリーススケジュールはありませんが、今すぐ早期アクセスバイナリをダウンロードして、状況を確認できます。 ただし、まだ非常に早いため、本番コードではこれに注意を払う必要があります。
5. コルーチン
コルーチンは、スレッディングとファイバーの代替手段です。 コルーチンは、スケジューリングの形式がないファイバーと考えることができます。 基盤となるシステムがいつでも実行しているタスクを決定する代わりに、コードはこれを直接実行します。
一般に、コルーチンは、フローの特定のポイントで生成されるように記述します。 これらは、関数の一時停止ポイントと見なすことができます。この場合、関数は機能を停止し、中間結果を出力する可能性があります。 譲歩すると、呼び出し元のコードが何らかの理由で再起動を決定するまで停止します。 これは、呼び出しコードがこれを実行するタイミングのスケジュールを制御することを意味します。
Kotlinは、標準ライブラリに組み込まれているコルーチンをネイティブでサポートしています。 必要に応じて、それらを実装するために使用できる他のJavaライブラリがいくつかあります。
6. 結論
従来のネイティブスレッドから非常に軽量な代替案まで、コード内のマルチタスクのさまざまな代替案を見てきました。 次回アプリケーションで同時実行が必要になったときに試してみませんか?