1. スレッドの概要
スレッドはプログラム実行の基本的な実行単位です。オペレーティング システム (Microsoft の初期の DOS などのシングルスレッド オペレーティング システムを除く) がプログラムを実行すると、システム内にプロセスが確立されます。このプロセスでは、少なくとも 1 つのスレッドを確立する必要があります (このスレッドはメイン スレッドと呼ばれます)。 thread) を、このプログラムを実行するためのエントリ ポイントとして使用します。したがって、オペレーティング システムで実行されるプログラムには、少なくとも 1 つのメイン スレッドがあります。
プロセスとスレッドは、最新のオペレーティング システムにおける 2 つの重要なオペレーティング モデルです。オペレーティング システムには、システム プロセス (オペレーティング システムによって内部的に作成されるプロセス) やユーザー プロセス (ユーザー プログラムによって作成されるプロセス) など、複数のプロセスが存在する場合があります。プロセスには 1 つ以上のスレッドが存在します。プロセス間に共有メモリはありません。つまり、システム内のプロセスは独自の独立したメモリ空間で実行されます。プロセス内のスレッドは、システムによってプロセスに割り当てられたメモリ空間を共有できます。
スレッドはプロセスのメモリを共有するだけでなく、独自のメモリ空間も持ちます。このメモリ空間は、スレッドの作成時にシステムによって割り当てられ、主にスレッドを保存するために使用されます。スレッドスタックなど、スレッド内で使用されるデータ。
注: スレッドは作成時に関数を実行します。この関数はスレッド実行関数と呼ばれます。この関数は、スレッドのエントリ ポイントとみなすこともできます (プログラムの main 関数と同様)。どのような言語やテクノロジを使用してスレッドを作成しても、この関数は必ず実行されます (この関数の表現は異なる場合がありますが、必ずこのような関数が存在します)。たとえば、Windows でスレッドを作成するために使用される API 関数 CreateThread の 3 番目のパラメーターは、この実行関数のポインターです。
オペレーティング システムがプロセスを複数のスレッドに分割すると、これらのスレッドはオペレーティング システムの管理下で同時に実行できるため、プログラムの実行効率が大幅に向上します。スレッドの実行は、マクロの観点からは複数のスレッドが同時に実行されているように見えますが、実際には、これはオペレーティング システムの隠蔽にすぎません。 CPU は一度に 1 つの命令しか実行できないため、1 つの CPU を備えたコンピュータで 2 つのタスクを同時に実行することはできません。プログラムの実行効率を向上させるために、オペレーティング システムはアイドル状態のスレッドを削除し、他のスレッドに実行させます。この方法はスレッド スケジューリングと呼ばれます。表面上で複数のスレッドが同時に実行されているのが見える理由は、異なるスレッド間の切り替え時間が非常に短く、通常の状況では切り替えが非常に頻繁に行われるためです。スレッド A と B があるとします。実行時、A が 1 ミリ秒間実行された後、B に切り替えた後、B がさらに 1 ミリ秒間実行され、その後 A に切り替わり、A がさらに 1 ミリ秒間実行される可能性があります。 1ミリ秒という単位は一般の人には認識しにくいため、表面上はAとBが同時に実行されているように見えますが、実際にはAとBが交互に実行されます。
2. スレッドがもたらす利点
スレッドを適切に使用すると、開発とメンテナンスのコストが削減され、複雑なアプリケーションのパフォーマンスも向上します。たとえば、GUI アプリケーションでは、スレッドの非同期特性によってイベントをより適切に処理できます。アプリケーション サーバー プログラムでは、クライアントの要求を処理するために複数のスレッドを確立できます。スレッドを使用すると、仮想マシンの実装も簡素化できます。たとえば、Java 仮想マシン (JVM) のガベージ コレクターは通常、1 つ以上のスレッドで実行されます。したがって、スレッドを使用すると、次の 5 つの側面でアプリケーションが改善されます。
1. CPUリソースを最大限に活用する
現在、世界中のほとんどのコンピューターには CPU が 1 つしか搭載されていません。したがって、CPU リソースを最大限に活用することが特に重要です。シングルスレッド プログラムを実行する場合、プログラムがブロックされている間 CPU がアイドル状態になることがあります。これにより、コンピューティング リソースが大量に無駄になります。プログラムでマルチスレッドを使用すると、スレッドがスリープまたはブロックされ、CPU がアイドル状態になったときに、他のスレッドを実行できます。このようにして、CPU がアイドル状態になりにくくなります。したがって、CPU リソースが最大限に活用されます。
2. プログラミング モデルを簡素化する
プログラムが 1 つのタスクのみを完了する場合は、シングルスレッド プログラムを作成し、タスクを実行する手順に従ってコードを記述します。ただし、複数のタスクを完了するには、依然として単一スレッドを使用する場合、プログラム内で各タスクを実行するかどうか、またいつ実行するかを決定する必要があります。たとえば、時計の時、分、秒が表示されます。シングルスレッドを使用する場合、ループ内でこれら 3 つのポインターの回転時間と角度を 1 つずつ判断する必要があります。これら 3 つのポインターの表示を処理するために 3 つのスレッドが使用される場合、スレッドごとに別個のタスクを実行することになります。これは、開発者がプログラムを理解し、保守するのに役立ちます。
3. 非同期イベントの処理を簡素化する
サーバー アプリケーションがさまざまなクライアント接続を受け入れる場合、これを処理する最も簡単な方法は、クライアント接続ごとにスレッドを作成することです。この場合も、リスニング スレッドはクライアントからのリクエストをリッスンする責任を負います。この種のアプリケーションが単一のスレッドで処理される場合、リスニング スレッドはクライアント リクエストを受信すると、クライアントから送信されたデータの読み取りを開始し、データの読み取り後、読み取りメソッド、つまりこのスレッドがブロックされます。クライアントのリクエストを聞くことができなくなります。複数のクライアント要求を 1 つのスレッドで処理する場合は、ノンブロッキングのソケット接続と非同期 I/O を使用する必要があります。ただし、非同期 I/O を使用すると、同期 I/O を使用する場合よりも制御が難しく、エラーが発生しやすくなります。したがって、マルチスレッドと同期 I/O を使用すると、複数のリクエストなどの非同期イベントを処理しやすくなります。
4. GUI をより効率的にする
単一スレッドを使用して GUI イベントを処理する場合、ループ内でいつでも発生する可能性のある GUI イベントをスキャンするためにループを使用する必要があり、GUI イベントのスキャンに加えて、他のプログラム コードも実行する必要があります。コードが長すぎる場合、コードが実行されるまで GUI イベントは「フリーズ」されます。
最新の GUI フレームワーク (SWING、AWT、SWT など) では、別のイベント ディスパッチ スレッド (EDT) を使用して GUI イベントをスキャンします。ボタンを押すと、ボタンのクリック イベント関数がこのイベント ディスパッチ スレッドで呼び出されます。 EDT のタスクは GUI イベントをスキャンすることだけであるため、このメソッドはイベントに非常に迅速に応答します。
5. コスト削減
プログラムの実行効率を向上させるには、一般に次の 3 つの方法があります。
(1) コンピュータの CPU の数を増やします。
(2) 1つのプログラムに対して複数のプロセスを起動する
(3) プログラム内で複数のプロセスを使用します。
最初の方法は最も簡単ですが、最も高価でもあります。この方法ではプログラムを変更する必要はなく、理論上、どのプログラムでもこの方法を使用して実行効率を向上させることができます。 2 番目の方法では、新しいハードウェアを購入する必要はありませんが、このプログラムで完了するタスクでデータの共有が必要な場合、この方法は不便であり、複数のスレッドを起動すると大量のシステムを消費します。リソース。 3 番目の方法は、最初の方法の利点を継承しながら、その欠点を補うだけです。言い換えれば、CPU を購入する必要はなく、多くのスレッドを起動して大量のシステム リソースを消費することもありません (デフォルトでは、スレッドが占有するメモリ領域はプロセスが占有するメモリ領域よりもはるかに小さくなります) )、マルチスレッドは複数の CPU の実行モードをシミュレートできるため、マルチスレッドの使用はプログラムの実行効率を向上させる最も安価な方法です。
3. Java スレッド モデル
Java は純粋なオブジェクト指向言語であるため、Java のスレッド モデルもオブジェクト指向です。 Java は、Thread クラスを通じて、スレッドに必要なすべての機能をカプセル化します。スレッドを作成するには、Thread クラスの run メソッドに対応するスレッド実行関数が必要です。 Thread クラスには、スレッドの確立を担当する start メソッドもあります。これは、Windows スレッド作成関数 CreateThread を呼び出すのと同じです。 start メソッドが呼び出されたとき、スレッドの確立に成功すると、Thread クラスの run メソッドが自動的に呼び出されます。したがって、Thread を継承する Java クラスは、Thread クラスの start メソッドを通じてスレッドを作成できます。独自のスレッド実行関数を実行する場合は、Thread クラスの run メソッドをオーバーライドする必要があります。
Java スレッド モデルの Thread クラスに加えて、Java クラスをスレッド クラスとして使用できるかどうかを識別する Runnable インターフェイスもあります。このインターフェイスには、Java のスレッド実行関数である抽象メソッドが 1 つだけ実行されます。スレッドモデル。したがって、スレッド クラスの唯一の基準は、そのクラスが Runnable インターフェイスの run メソッドを実装しているかどうかです。つまり、スレッド実行機能を持つクラスがスレッド クラスです。
上記から分かるように、Java でスレッドを作成するには 2 つの方法があります。1 つは Thread クラスを継承する方法で、もう 1 つは Runnable インターフェイスを実装し、Thread と Runnable を実装するクラスを通じてスレッドを作成する方法です。これら 2 つのメソッドは本質的に、Thread クラスを通じてスレッドが作成され、run メソッドが実行されるメソッドであると言われます。ただし、大きな違いは、スレッドが Thread クラスを継承することによって作成されることです。実装は簡単ですが、Java は多重継承をサポートしていないため、このスレッド クラスが Thread を継承すると、他のクラスを継承できなくなります。 Runnable インターフェイスを実装することによってスレッドを確立するメソッド。これにより、スレッド クラスは必要に応じて Thread クラスの代わりにビジネス関連のクラスを継承できます。