Java でのマルチスレッド化

Java 仮想マシンは並列コンピューティングをサポートします。すべての計算は 1 つ以上のスレッドのコンテキストで実行できます。複数のスレッドで同じリソースまたはオブジェクトへのアクセスを簡単に設定できるだけでなく、単一のコード ブロックを実行するようにスレッドを設定することもできます。

開発者は、複数のスレッドが割り当てられているリソースの読み取りおよび書き込み操作中に、スレッドと作業を同期する必要があります。

リソースにアクセスするときに、別のスレッドがデータを変更して最新の情報を取得できるように、最新のデータを持っていることが重要です。銀行口座を例にしても、お金が入金されるまでは使えないので、常に最新のデータを持っておくことが重要です。Java には、スレッドの同期と管理のための特別なクラスがあります。

スレッドオブジェクト

すべてはメイン (メイン) スレッドから始まります。つまり、プログラムには少なくとも 1 つの実行スレッドがすでに存在します。メインスレッドは、CallableまたはRunnableを使用して他のスレッドを作成できます。作成は返される結果のみが異なります。Runnable結果を返さず、チェック例外をスローできません。したがって、ファイルを使用して効率的な作業を構築する良い機会になりますが、これは非常に危険なので注意が必要です。

別の CPU コアでスレッドの実行をスケジュールすることもできます。システムはスレッド間を簡単に移動し、適切な設定で特定のスレッドを実行できます。つまり、データを取得するとすぐにデータを読み取るスレッドが最初に実行され、次にそれを検証を担当するスレッドに渡します。その後、それをスレッドに渡してビジネス ロジックを実行し、新しいスレッドがそれらを書き戻します。このような状況では、4 つのスレッドが順番にデータを処理するため、すべてが 1 つのスレッドよりも高速に動作します。このような各ストリームはネイティブ OS ストリームに変換されますが、変換方法は JVM 実装によって異なります。

Threadクラスは、スレッドの作成と操作に使用されます。標準的な制御メカニズムだけでなく、java.util.concurrentのクラスやコレクションなどの抽象的な制御メカニズムも備えています。

Java でのスレッド同期

通信は、オブジェクトへのアクセスを共有することによって提供されます。これは非常に効果的ですが、同時に作業中に非常に間違いを犯しやすくなります。エラーには 2 つのケースがあります。1 つはスレッド干渉 - 別のスレッドがスレッドに干渉する場合、もう 1 つはメモリ整合性エラー - メモリ整合性です。これらのエラーを解決および防止するために、さまざまな同期方法が用意されています。

Java のスレッド同期はモニターによって処理されます。これは、同じモニターによって保護されているコードのブロックを一度に 1 つのスレッドのみが実行できるようにする高レベルのメカニズムです。モニターの動作はロックの観点から考慮されます。1 つのモニター - 1 つのロック。

同期には、注意すべき重要な点がいくつかあります。最初のポイントは相互排他です - 1 つのスレッドのみがモニターを所有できるため、モニターでの同期は、1 つのスレッドがモニターによって保護された同期ブロックに入ると、他のスレッドはモニターによって保護されたブロックに入ることができないことを意味します。このモニターは、最初のスレッドは同期ブロックを終了します。つまり、複数のスレッドが同じ同期ブロックに同時にアクセスすることはできません。

しかし、同期は相互排除だけではありません。同期により、同期ブロックの前または同期ブロック内でメモリに書き込まれたデータが、同じモニター上で同期されている他のスレッドから見えるようになります。ブロックを終了した後、モニターを解放すると、別のスレッドがモニターを取得して、このコード ブロックの実行を開始できます。

新しいスレッドがモニターをキャプチャすると、そのコード ブロックにアクセスして実行できるようになり、その時点で変数がメイン メモリからロードされます。次に、モニターの以前のリリースで表示されたすべてのエントリを確認できます。

フィールドが揮発性であると宣言されているか、読み取り/書き込みの前に取得された一意のロックによって保護されている場合、フィールドの読み取り/書き込みはアトミック操作です。ただし、それでもエラーが発生する場合は、並べ替えに関するエラー (順序の変更、並べ替え) が表示されます。これは、誤って同期されたマルチスレッド プログラムに現れ、あるスレッドが他のスレッドによって生成された影響を観察することができます。

スレッドの相互排他と同期の効果、つまりスレッドの正しい動作は、暗黙的にロックを取得する同期ブロックまたはメソッドに入るか、明示的にロックを取得することによってのみ実現されます。それについては以下で説明します。どちらの作業方法も記憶に影響を与えるため、揮発性変数の操作を忘れないことが重要です。

Javaの揮発性フィールド

変数がvolatile とマークされている場合、その変数はグローバルに使用できます。これは、スレッドが揮発性変数にアクセスする場合、キャッシュからの値を使用する前にその値を取得することを意味します。

書き込みはモニターのリリースのように機能し、読み取りはモニターのキャプチャのように機能します。アクセスは「以前に実行された」タイプの関係で行われます。理解できれば、スレッド A が揮発性変数にアクセスするときに見えるのは、スレッド B の変数だけです。つまり、他のスレッドからの変更が失われないことが保証されます。

揮発性変数はアトミックです。つまり、そのような変数を読み取るときは、ロックを取得するときと同じ効果が使用されます。メモリ内のデータは無効または不正であると宣言され、揮発性変数の値がメモリから再度読み取られます。書き込み時には、メモリへの影響が使用され、ロックを解放するときにも揮発性フィールドがメモリに書き込まれます。

Java 同時実行

非常に効率的なマルチスレッド アプリケーションを作成したい場合は、 java.util.concurrentパッケージに含まれるJavaConcurrentライブラリのクラスを使用する必要があります。

このライブラリは非常にボリュームがあり、さまざまな機能を備えているため、内部にあるものを見ていくつかのモジュールに分割してみましょう。

Java 同時実行

同時コレクションは、マルチスレッド環境で作業するためのコレクションのセットです。コレクション全体へのアクセスをブロックする基本的なラッパー Collections.synchronizedList の代わりに、データ セグメントに対してロックが使用されるか、待機なしアルゴリズムが使用されてデータを並行して読み取ります。

キュー- マルチスレッド環境で作業するためのノンブロッキング キューとブロッキング キュー。ノンブロッキング キューは、スレッドをブロックせずに速度と操作を重視します。ブロッキング キューは、プロデューサースレッドまたはコンシューマースレッドの「速度を下げる」必要がある場合の作業に適しています。たとえば、いくつかの条件が満たされない状況では、キューが空か満杯であるか、空いているコンシューマ'a が存在しません。

シンクロナイザーは、スレッドを同期するためのユーティリティです。これらは「並列」コンピューティングにおける強力な武器です。

Executors は、スレッド プールをより便利かつ簡単に作成するためのフレームワークであり、結果を取得する非同期タスクのスケジュールを簡単に設定できます。

ロックは、基本的なsynchronized wait notify notifyAllと比較して、多くの柔軟なスレッド同期メカニズムです。

アトミックは、プリミティブおよび参照に対するアトミック操作をサポートできるクラスです。