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 つのスレッドがモニターによって保護された同期ブロックに入ると、他のスレッドはモニターによって保護されたブロックに入ることができないことを意味します。このモニターは、最初のスレッドは同期ブロックを終了します。つまり、複数のスレッドが同じ同期ブロックに同時にアクセスすることはできません。
しかし、同期は相互排除だけではありません。同期により、同期ブロックの前または同期ブロック内でメモリに書き込まれたデータが、同じモニター上で同期されている他のスレッドから見えるようになります。ブロックを終了した後、モニターを解放すると、別のスレッドがモニターを取得して、このコード ブロックの実行を開始できます。
新しいスレッドがモニターをキャプチャすると、そのコード ブロックにアクセスして実行できるようになり、その時点で変数がメイン メモリからロードされます。次に、モニターの以前のリリースで表示されたすべてのエントリを確認できます。
フィールドがvolatileと宣言されている場合(long と double ではこれが重要です)、またはそのフィールドへのすべてのアクセスが同じロックで保護されている場合、フィールドの個々の読み取りまたは書き込みはアトミックに実行されます。同期がない場合は、並べ替え(reordering)の影響が生じることがあります。正しく同期されていないプログラムでは、あるスレッドが別のスレッドによる変更を予期しない順序で見る可能性があります。
スレッドの相互排他と同期の効果、つまりスレッドの正しい動作は、暗黙的にロックを取得する同期ブロックまたはメソッドに入るか、明示的にロックを取得することによってのみ実現されます。それについては以下で説明します。どちらの作業方法も記憶に影響を与えるため、揮発性変数の操作を忘れないことが重要です。
Javaの揮発性フィールド
キーワードvolatileは可視性の問題を解決します。あるスレッドがvolatileフィールドに値を書き込んだ場合、その後でそのフィールドを読み取る他のスレッドはどれも、古いコピーではなく新しい値を見ることになります。
メモリへの効果という点では、volatileフィールドへの書き込みはモニターの解放に、読み取りはモニターの取得に似ています。スレッド A がvolatileフィールドへの書き込みより前に書き込んだものはすべて、スレッド B がそのフィールドから書き込まれた値を読み取った場合、スレッド B から確実に見えます。この関係は happens-before(「先に起こる」)と呼ばれます。
volatileフィールドの個々の読み取りまたは書き込みは、long と double を含めてアトミックです。しかし、count++(読み取り、インクリメント、書き込み)のような複合操作はアトミックではありません。ステップの間に別のスレッドが割り込む可能性があり、インクリメントの一部が失われます。volatileは相互排他を提供しません。複合操作をアトミックに実行するには、synchronized、Lock、または AtomicInteger、AtomicLong などのクラスが必要です。
Java 同時実行
非常に効率的なマルチスレッド アプリケーションを作成したい場合は、 java.util.concurrentパッケージに含まれるJavaConcurrentライブラリのクラスを使用する必要があります。
このライブラリは非常にボリュームがあり、さまざまな機能を備えているため、内部にあるものを見ていくつかのモジュールに分割してみましょう。
同時コレクションは、マルチスレッド環境で作業するためのコレクションのセットです。コレクション全体へのアクセスをブロックする基本的なラッパー Collections.synchronizedList の代わりに、データ セグメントに対してロックが使用されるか、待機なしアルゴリズムが使用されてデータを並行して読み取ります。
キュー- マルチスレッド環境で作業するためのノンブロッキング キューとブロッキング キュー。ノンブロッキング キューは、スレッドをブロックせずに速度と操作を重視します。ブロッキング キューは、プロデューサースレッドまたはコンシューマースレッドの「速度を下げる」必要がある場合の作業に適しています。たとえば、いくつかの条件が満たされない状況では、キューが空か満杯であるか、空いているコンシューマ'a が存在しません。
シンクロナイザーは、スレッドを同期するためのユーティリティです。これらは「並列」コンピューティングにおける強力な武器です。
Executors は、スレッド プールをより便利かつ簡単に作成するためのフレームワークであり、結果を取得する非同期タスクのスケジュールを簡単に設定できます。
ロックは、基本的なsynchronized、 wait、 notify、 notifyAllと比較して、多くの柔軟なスレッド同期メカニズムです。
アトミックは、プリミティブおよび参照に対するアトミック操作をサポートできるクラスです。
GO TO FULL VERSION