1. 概要

この記事では、 ProjectLoomについて簡単に説明します。 本質的に、 Project Loomの主な目標は、Javaで高スループットで軽量の同時実行モデルをサポートすることです。

2. プロジェクトルーム

Project Loomは、Javaに軽量の並行性構造を導入するためのOpenJDKコミュニティによる試みです。 これまでのLoomのプロトタイプでは、JVMとJavaライブラリに変更が加えられています。

Loomのリリースはまだ予定されていませんが、ProjectLoomのwikiで最近のプロトタイプにアクセスできます。

Loomのさまざまな概念について説明する前に、Javaの現在の同時実行モデルについて説明しましょう。

3. Javaの並行性モデル

現在、スレッドは、Javaの並行性のコア抽象化を表しています。 この抽象化は、他の並行API とともに、並行アプリケーションの作成を容易にします。

ただし、Javaは実装に OSカーネルスレッドを使用するため、今日の同時実行性の要件を満たすことができません。 特に2つの大きな問題があります。

  1. スレッドは、ドメインの同時実行単位のスケールと一致できません。 たとえば、アプリケーションは通常、最大数百万のトランザクション、ユーザー、またはセッションを許可します。 ただし、カーネルでサポートされるスレッドの数ははるかに少なくなります。 したがって、すべてのユーザー、トランザクション、またはセッションのスレッドは実行できないことがよくあります。
  2. ほとんどの並行アプリケーションでは、要求ごとにスレッド間の同期が必要です。 このため、OSスレッド間で高価なコンテキストスイッチが発生します。

このような問題に対する可能な解決策は、非同期同時APIの使用です。 一般的な例は、CompleteableFutureおよびRxJavaです。 このようなAPIがカーネルスレッドをブロックしない場合、アプリケーションはJavaスレッドの上にさらにきめ細かい同時実行構造を提供します。

一方、このようなAPIは、デバッグやレガシーAPIとの統合が困難です。 したがって、カーネルスレッドに依存しない軽量の同時実行構造が必要です。

4. タスクとスケジューラ

スレッドの実装は、軽量または重量のいずれかで、次の2つの構成に依存します。

  1. タスク(継続とも呼ばれます)–一部のブロッキング操作のためにそれ自体を一時停止できる一連の命令
  2. スケジューラ– CPUに継続を割り当て、一時停止した継続からCPUを再割り当てします

現在、 Javaは、継続とスケジューラーの両方をOS実装に依存しています。

ここで、継続を一時停止するには、コールスタック全体を保存する必要があります。 同様に、再開時にコールスタックを取得します。 継続のOS実装には、Javaのコールスタックとともにネイティブのコールスタックが含まれているため、フットプリントが大きくなります

ただし、より大きな問題は、OSスケジューラの使用です。 スケジューラはカーネルモードで実行されるため、スレッド間の違いはありません。 また、すべてのCPU要求を同じように処理します。

このタイプのスケジューリングは、特にJavaアプリケーションには最適ではありません。

たとえば、リクエストに対して何らかのアクションを実行し、さらに処理するためにデータを別のスレッドに渡すアプリケーションスレッドについて考えてみます。 ここで、これらの両方のスレッドを同じCPUでスケジュールすることをお勧めします。 ただし、スケジューラはCPUを要求するスレッドに依存しないため、これを保証することはできません。

Project Loomは、OS実装ではなく継続とスケジューラのJavaランタイム実装に依存するユーザーモードスレッドを通じてこれを解決することを提案しています。

5. 繊維

OpenJDKの最近のプロトタイプでは、Fiberという名前の新しいクラスがThreadクラスと一緒にライブラリに導入されています。

ファイバーの計画されたライブラリはスレッドに類似しているため、ユーザー実装も類似したままである必要があります。 ただし、主に2つの違いがあります。

  1. ファイバだろうすべてのタスクを内部ユーザーモードの継続でラップします。 これにより、タスクはカーネルではなくJavaランタイムで一時停止および再開できます。
  2. プラグ可能なユーザーモードスケジューラ( ForkJoinPool、など)が使用されます

これら2つの項目を詳しく見ていきましょう。

6. 続き

継続(またはコルーチン)は、呼び出し元が後の段階で生成して再開できる一連の命令です。

すべての継続には、エントリポイントと降伏ポイントがあります。 降伏点はそれが中断された場所です。 呼び出し元が継続を再開するたびに、コントロールは最後の降伏点に戻ります。

このサスペンド/レジュームがOSではなく言語ランタイムで発生するようになったことをで理解することが重要です。 したがって、カーネルスレッド間の高価なコンテキストスイッチを防ぎます。

スレッドと同様に、ProjectLoomはネストされたファイバーをサポートすることを目的としています。 ファイバーは内部で継続に依存しているため、ネストされた継続もサポートする必要があります。 これをよりよく理解するために、ネストを可能にするクラス継続を検討してください。

Continuation cont1 = new Continuation(() -> {
    Continuation cont2 = new Continuation(() -> {
        //do something
        suspend(SCOPE_CONT_2);
        suspend(SCOPE_CONT_1);
    });
});

上に示したように、ネストされた継続は、スコープ変数を渡すことにより、それ自体またはそれを囲む継続のいずれかを一時停止できます。 。 このために、 それらはスコープ継続として知られています。

継続を一時停止すると、呼び出しスタックを格納する必要もあるため、継続を再開しながら軽量スタックの取得を追加することもProjectLoomの目標です。

7. スケジューラー

以前、同じCPUで関連するスレッドをスケジュールする際のOSスケジューラの欠点について説明しました。

Project Loomの目標は、ファイバーを使用してプラグ可能なスケジューラーを許可することですが、 非同期モードのForkJoinPoolがデフォルトのスケジューラーとして使用されます。 

ForkJoinPool は、ワークスティーリングアルゴリズムで動作します。 したがって、すべてのスレッドはタスクの両端キューを維持し、そのヘッドからタスクを実行します。 さらに、アイドル状態のスレッドはブロックされません。 タスクを待機し、代わりに別のスレッドの両端キューの末尾からタスクをプルします。 

非同期モードの唯一の違いは、ワーカースレッドが別の両端キューの先頭からタスクを盗むことです。

ForkJoinPool 別の実行中のタスクによってスケジュールされたタスクをローカルキューに追加します。 したがって、同じCPUで実行します。 

8. 結論

この記事では、Javaの現在の同時実行モデルの問題と、 ProjectLoomによって提案された変更について説明しました。

その際、タスクとスケジューラーも定義し、FibersとForkJoinPoolがカーネルスレッドを使用してJavaの代替手段を提供する方法を検討しました。