Javaプログラミング言語とJava 仮想マシン(JVM) は、並行プログラミングをサポートするように設計されています。すべての実行はスレッドのコンテキストで行われます。オブジェクトとリソースは、多数の独立したスレッドからアクセスできます。各スレッドは独自の実行パスを持ちますが、プログラム内の任意のオブジェクトにアクセスできます。オブジェクトへの読み取りおよび書き込みアクセスは、スレッド間で適切に調整 (または「同期」) する必要があります。[ 1 ] [ 2 ]スレッド同期により、オブジェクトは一度に 1 つのスレッドによってのみ変更され、別のスレッドによる変更中に、スレッドが部分的に更新されたオブジェクトにアクセスすることが防止されます。[ 2 ]
Java仮想マシンのほとんどの実装は、単一プロセスとして実行されます。Javaプログラミング言語では、並行プログラミングは主にスレッド(軽量プロセスとも呼ばれる)に関係します。複数のプロセスは、複数のJVMを使用することによってのみ実現できます。
スレッドは、メモリや開いているファイルなど、プロセスのリソースを共有します。これにより、効率的な通信が可能になりますが、問題が発生する可能性もあります。[ 2 ]すべてのアプリケーションには、メインスレッドと呼ばれるスレッドが少なくとも 1 つあります。メインスレッドは、RunnableまたはCallableオブジェクトとして追加のスレッドを作成する機能を持っています。インターフェースは、インスタンスが別のスレッドによって実行される可能性があるクラス用に設計されているという点で、java.lang.Callableに似ています。 [ 3 ]ただし、は結果を返さず、チェック例外をスローすることはできません。[ 4 ]java.lang.Runnablejava.lang.Runnable
各スレッドは、異なるCPUコア[ 6 ]にスケジュールすることも[ 5 ] 、単一のハードウェアプロセッサでタイムスライシングを使用することも、複数のハードウェアプロセッサでタイムスライシングを使用することもできます。JavaスレッドをネイティブOSスレッドにマッピングする方法には一般的な解決策はありません。JVMの実装ごとに異なる方法でマッピングできます。
各スレッドは、クラスのインスタンスに関連付けられています。スレッドは、オブジェクトを直接使用して管理することも、または、やタスク クラスなどの抽象メカニズムを使用して間接的にjava.lang.Thread管理することもできます。[ 7 ] Java は、次のタスク クラスを提供します。java.lang.Threadjava.util.concurrent.Executor
java.util.concurrent.ForkJoinTask<V>、そしてその子孫たち: java.util.concurrent.CountedCompleter<T>java.util.concurrent.RecursiveAction(はForkJoinTask<Void>)java.util.concurrent.RecursiveTask<V>java.util.concurrent.FutureTask<V>javafx.concurrent.Task<V>( JavaFXより)Java 21 のProject Loomの一部として仮想スレッドが導入され、数百万のタスクにスケーリングしながら、通常のブロッキング同期コードを実行できるようになりました。[ 8 ] [ 9 ]従来の Java スレッドは「プラットフォーム スレッド」と呼ばれ、おおよそ OS スレッドに相当しますが、Java 仮想スレッドは軽量で、JVM によって管理され、実際の OS スレッドの小さなプールにスケジュールされます。仮想スレッドは依然として同期ですjava.lang.Threadが、などのファクトリ メソッドを使用して作成されますofVirtual()。コードは依然として同期でブロッキングですが、物理的な OS スレッドは非同期です。待機のために停止すると、JVM はコードを物理スレッドから切り離し、OS が非同期イベント通知を送信して中断したコードを引き継いで完了するまで、他のタスクを実行します。
スレッドは、java.lang.Runnableオブジェクトを提供することによって開始できます。
public class HelloRunnable implements Runnable { @Override public void run () { System . out . println ( "スレッドからのこんにちは!" ); }public static void main ( String [] args ) { Thread t = new Thread ( new HelloRunnable ()); t . start (); } }または、サブクラス化によって:
public class HelloThread extends Thread { @Override public void run () { System . out . println ( "スレッドからのこんにちは!" ); }public static void main ( String [] args ) { HelloThread t = new HelloThread (); t . start (); } }割り込みは、スレッドに実行中の処理を停止して別の処理を実行するように指示します。スレッドは、割り込み対象のスレッドinterrupt()に対してを呼び出すことで割り込みを送信しますjava.lang.Thread。割り込みメカニズムは、 の呼び出し時に設定される、割り込み状態を示す内部フラグを使用して実装されますinterrupt()。[ 10 ] [ 11 ]をスローして終了するメソッドは、java.lang.InterruptedException終了時に割り込み状態をクリアしますが、別のスレッドが を呼び出すことで、割り込み状態がすぐに再び設定される可能性がありますinterrupt()。
従来、このメソッドを使用するとスレッドを即座に停止することができましたstop()。このメソッドを呼び出すとjava.lang.ThreadDeath例外がスローされ、スレッドがクリアされます。このメソッドはデータが破損した状態になる可能性があるため危険であり、最終的にJava 20で削除されました。[ 12 ]
このjava.lang.Thread#join()方法により、ある処理がjava.lang.Thread別の処理の完了を待つことができる。
コードによってスローされた捕捉されない例外はスレッドを終了させます。メインスレッドは例外をコンソールに出力しますが、ユーザーが作成したスレッドでは、例外を出力するハンドラを登録する必要があります。[ 13 ] [ 14 ]
Javaメモリ モデルは、 Java プログラミング言語のスレッドがメモリを介してどのように相互作用するかを記述します。最新のプラットフォームでは、コードは記述された順序で実行されることはあまりありません。パフォーマンス上の理由から、コンパイラ、プロセッサ、およびメモリ サブシステムによって順序が変更されます。共有オブジェクトのフィールドを読み書きする際には、線形化可能性や逐次一貫性さえ保証されません[ 15 ] 。これにより、メモリの読み書きの順序を変更することによって、コンパイラの最適化(レジスタ割り当て、共通部分式の削除、冗長な読み取りの削除など)が可能になります[ 16 ] 。
スレッドは主に、フィールドへのアクセスと、参照フィールドが参照するオブジェクトへのアクセスを共有することによって通信します。この通信方法は非常に効率的ですが、スレッド干渉とメモリ整合性エラーという2種類のエラーが発生する可能性があります。
同期が正しく行われていないマルチスレッドプログラムでは、順序の変更が発生する可能性があります。これは、あるスレッドが他のスレッドの影響を監視でき、変数へのアクセスがプログラムで実行または指定された順序とは異なる順序で他のスレッドから見えるようになることを検出できる可能性があるためです。
スレッド同士がやり取りする必要がある場合、同期が使用されます。Javaでは、スレッドを同期するためにモニターを使用します。モニターは、モニターによって保護されたコード領域を一度に1つのスレッドのみが実行できるようにする高レベルのメカニズムです。モニターの動作はロックの観点から説明され、各オブジェクトにはロックが関連付けられています。
同期には多くの実装方法があります。特に注目すべきは相互排他で、一度にモニターを保持できるスレッドは1つだけなので、モニターで同期するということは、1つのスレッドがモニターで保護された同期ブロックに入ると、最初のスレッドが同期ブロックから出るまで、他のスレッドはそのモニターで保護されたブロックに入ることができないということです。[ 2 ]
同期処理により、同期ブロックの前または実行中にスレッドが行ったメモリ書き込みが、同じモニタ上で同期する他のスレッドから予測可能な方法で可視化されます。synchronizedブロックが完了するとモニタが解放され、キャッシュがメインメモリにフラッシュされるため、このスレッドが行った書き込みが他のスレッドから見えるようになります。synchronizedブロックの開始前にモニタが取得され、ローカルプロセッサキャッシュが無効化されるため、変数がメインメモリから再ロードされます。この時点で、前回の解放によって行われたすべての書き込みが可視化されます。
フィールドへの読み書きは、フィールドが揮発性であるか、またはすべての読み取り者と書き込み者が取得する一意のロックによってフィールドが保護されている場合に、線形化可能です。
スレッドは、暗黙のロックを取得する同期ブロックまたはメソッドに入るか、明示的なロック(パッケージのロックなど)を取得することによって相互排他を実現できます。 [ 17 ] [ 2 ]どちらの方法もメモリ動作に同じ影響を与えます。特定のフィールドへのすべてのアクセスが同じロックで保護されている場合、そのフィールドへの読み取りと書き込みは線形化可能(アトミック)になります。ReentrantLockjava.util.concurrent.locks
JVM は実行時にロックを適用することもあります。これは「ストアフリーバイアスロック技術」または「バイアスロック」として知られています。[ 19 ]
キーワードをフィールドに適用すると、変数volatileへの読み取りと書き込みにグローバルな順序付けが保証されますvolatile。これは、フィールドにアクセスするすべてのスレッドがvolatile、キャッシュされた値を使用する(可能性のある)のではなく、処理を続行する前に現在の値を読み取ることを意味します。ただし、揮発性変数の読み取りと書き込みと通常の読み取りと書き込みの相対的な順序については保証されません。
Java 5以降、volatile読み取りと書き込みは、ミューテックスの取得と解放と同様に、happens-before関係を確立します。 [ 20 ]この関係は、ある特定のステートメントによるメモリ書き込みが、別の特定のステートメントから見えることを保証するものです。
volatileフィールドは線形化可能です。volatileフィールドの読み取りは、ロックを取得することに似ています。作業メモリが無効化され、volatileフィールドの現在の値がメモリから再読み取りされます。volatileフィールドの書き込みは、ロックを解除することに似ています。volatileフィールドはすぐにメモリに書き戻されます。
として宣言されたフィールドは、final初期化されると変更できません。[ 21 ]オブジェクトのfinalフィールドはコンストラクタで初期化されます。コンストラクタが戻る前に参照がコンストラクタから解放されない限りthis、同期なしで他のスレッドから任意のフィールドの正しい値をfinal見ることができます。[ 22 ]
Java は、アトミック性機能を提供しますjava.util.concurrent.atomic。これらは、ロックフリーでスレッドセーフなアトミック操作を備えた型を提供し、同時に を回避しますsynchronized。
プリミティブ型とプリミティブ型の配列には独自のアトミック型(やなど)がありjava.util.concurrent.atomic.AtomicInteger、java.util.concurrent.atomic.AtomicIntegerArray参照型にはクラスjava.util.concurrent.atomic.AtomicReference<T>が提供されています。
import java.util.concurrent.atomic.AtomicReference ;AtomicReference < String > ref = new AtomicReference <> ( "Hello..." ); ref . set ( "...world!" ); boolean success = ref . compareAndSet ( "...world!" , "Hello, world!" ); System . out . println ( ref . get ()); // "Hello, world!"の代わりに、非同期計算java.lang.Thread用の別のクラスjava.util.concurrent.CompletableFuture<T>が提供されています[ 23 ] 。これはC#のに似ていると見なすことができますが、JavaにはC#のような明示的なコルーチンはありません。System.Threading.Tasks.Task<T>
JDK 1.2以降、Javaには標準的なコレクションクラス群であるJavaコレクションフレームワークが組み込まれています。
Javaコレクションフレームワークの実装にも参加したダグ・リーは、いくつかの並行処理プリミティブと多数のコレクション関連クラスを含む並行処理パッケージを開発しました。 [ 24 ]この作業は、ダグ・リーが議長を務めたJSR 166の一部として継続および更新されました。
JDK 5.0では、Javaの並行処理モデルに多くの追加機能と明確化が盛り込まれました。JSR 166で開発された並行処理APIも、初めてJDKの一部として組み込まれました。JSR 133は、マルチスレッド/マルチプロセッサ環境における明確に定義されたアトミック操作のサポートを提供しました。
Java SE 6とJava SE 7の両リリースでは、JSR 166 APIの更新版に加え、いくつかの新しいAPIが導入されました。
注: J2SE 5.0 のリリースに伴い、このパッケージはメンテナンス モードになります。重要な修正のみがリリースされます。J2SE5 パッケージ java.util.concurrent には、このパッケージの主要コンポーネントの改良された、より効率的な、標準化されたバージョンが含まれています。