-
同じプロセスの複数のスレッドが同じ記憶領域を共有するため、利便性をもたらす一方で、アクセス競合という深刻な問題も引き起こします。 Java 言語は、この競合を解決するための特別なメカニズムを提供し、複数のスレッドが同じデータ オブジェクトに同時にアクセスすることを効果的に防止します。
private キーワードを使用すると、データ オブジェクトにメソッドのみがアクセスできるようにすることができるため、必要なのはメソッドのメカニズムを提案することだけです。このメカニズムには、synchronized メソッドと synchronized ブロックという 2 つの使用法が含まれます。
1. synchronized メソッド: メソッド宣言に synchronized キーワードを追加して、synchronized メソッドを宣言します。のように:
public synchronized void accessVal(int newVal);
同期メソッドは、クラス メンバー変数へのアクセスを制御します。各クラス インスタンスはロックに対応します。各同期メソッドは、メソッドを実行する前に、そのメソッドを呼び出すクラス インスタンスのロックを取得する必要があります。取得しないと、そのメソッドが属するスレッドはブロックされます。メソッドが実行されると、そのメソッドはクラス メンバー変数を排他的に占有します。その後、ブロックされたスレッドはロックを取得し、実行可能状態に戻ります。このメカニズムにより、各クラス インスタンスに対して、同期済みとして宣言されたすべてのメンバー関数のうち最大 1 つが実行可能状態になることが保証されます (クラス インスタンスに対応するロックを取得できるのは最大 1 つだけであるため) (このステートメントに注意してください) ! これはクラス インスタンスのロックであるため、オブジェクトごとに一度に 1 つの同期メソッドのみが 1 つのスレッドによって実行されるため、クラス メンバー変数のアクセス競合が効果的に回避されます (すべてのメソッドが実行される場合に限ります)。アクセス クラスのメンバー変数は同期済みとして宣言されます)。
Java では、クラス インスタンスだけでなく、各クラスもロックに対応します。このように、クラスの静的メンバー関数を同期済みとして宣言して、クラスの静的メンバー変数へのアクセスを制御することもできます。
同期メソッドの欠点: 大きなメソッドが同期済みとして宣言されている場合、通常、スレッド クラスのメソッド run() が同期済みとして宣言されている場合、スレッドのライフ サイクル全体にわたって実行され続けます。このクラスの同期されたメソッドへの呼び出しは失敗します。もちろん、クラス メンバー変数にアクセスするコードを特別なメソッドに組み込み、それを synchronized として宣言し、main メソッドで呼び出すことでこの問題を解決できますが、Java はより良い解決策、つまり synchronized ブロックを提供します。
2. synchronized block: synchronized キーワードを使用して synchronized ブロックを宣言します。構文は次のとおりです。
同期化(syncObject) {
// アクセス制御を許可するコード
}
同期ブロックは、コードが実行される前にオブジェクト syncObject (前述したように、クラス インスタンスまたはクラス) のロックを取得する必要があるコード ブロックです。その具体的なメカニズムは上記と同じです。任意のコードブロックを対象とし、ロック対象を任意に指定できるため、柔軟性が高い。
スレッドのブロック (同期)
共有ストレージ領域へのアクセスの競合を解決するために、Java は同期メカニズムを導入しました。次に、複数のスレッドによる共有リソースへのアクセスを調べてみましょう。いつでも必要なリソースが得られるわけではないため、同期メカニズムだけでは十分ではありません。アクセスするために、同時に複数のリソースが準備されている可能性があります。この場合のアクセス制御の問題を解決するために、Java ではブロック メカニズムのサポートが導入されました。
ブロッキングとは、スレッドの実行を一時停止して、特定の条件 (リソースの準備が完了するなど) が発生するのを待つことを指します。オペレーティング システムを学習したことのある学生はよく知っているはずです。 Java にはブロッキングをサポートする多数のメソッドが用意されています。それらを 1 つずつ分析してみましょう。
1. sleep() メソッド: sleep() を使用すると、パラメータとして時間をミリ秒単位で指定できます。これにより、スレッドは指定された時間内にブロック状態になり、指定された時間が経過すると CPU 時間を取得できなくなります。スレッドは実行可能状態に戻ります。
通常、sleep() はリソースの準備ができるのを待つときに使用されます。テストで条件が満たされていないことが判明した後、一定期間スレッドをブロックしてから、条件が満たされるまで再テストします。
2. サスペンド() メソッドとレジューム() メソッド (デッドロックを引き起こしやすい、時代遅れ): この 2 つのメソッドを一緒に使用すると、スレッドがブロッキング状態になり、対応するレジューム() は自動的に回復しません。が呼び出された場合、スレッドは実行可能状態に戻ることができます。通常、suspend() とresume() は、別のスレッドによって生成された結果を待機するときに使用されます。テストで結果が生成されていないことが判明した後、スレッドはブロックされ、別のスレッドが結果を生成した後、resume() を呼び出します。再開します。
3. yield() メソッド: yield() は、スレッドに現在割り当てられている CPU 時間を放棄させますが、スレッドをブロックさせません。つまり、スレッドはまだ実行可能な状態にあり、次の時点で再度 CPU 時間を割り当てることができます。いつでも。 yield() を呼び出すことの効果は、スケジューラーがスレッドが別のスレッドに移動するのに十分な時間を実行したとみなすことと同じです。
4. wait() メソッドと notify() メソッド: この 2 つのメソッドは一緒に使用され、スレッドをブロッキング状態にします。1 つはパラメータとしてミリ秒単位で指定できます。他のパラメータはそうではありません。前者は、対応するnotify()が呼び出されるか、指定された時間が経過すると、実行可能状態に戻ります。後者は、対応するnotify()が呼び出されるときに呼び出される必要があります。
一見すると、suspend() メソッドとresume() メソッドのペアと何ら変わらないように見えますが、実際にはまったく異なります。主要な違いは、上記のすべてのメソッドはブロック時に占有されているロックを (占有されている場合) 解放しないのに対し、この反対のルールはその逆であることです。
上記の主要な違いは、一連の詳細な違いにつながります。
まず第一に、上で説明したすべてのメソッドは Thread クラスに属しますが、このペアは直接 Object クラスに属します。つまり、すべてのオブジェクトがこのメソッドのペアを持ちます。これは最初は信じられないように思えるかもしれませんが、実際には非常に自然なことです。このペアのメソッドがブロックされると、占有されているロックを解放する必要があり、そのロックは任意のオブジェクトによって所有されるためです。オブジェクトの wait() メソッドを呼び出すと、スレッドがブロックされ、オブジェクトのロックが解除されます。任意のオブジェクトのnotify()メソッドを呼び出すと、オブジェクトのwait()メソッドを呼び出すことによってブロックされていたランダムに選択されたスレッドがブロック解除されます(ただし、ロックが取得されるまで実行されません)。
第 2 に、上で説明したすべてのメソッドは任意の場所で呼び出すことができますが、このメソッドのペア (wait() と notify()) は同期メソッドまたはブロックで呼び出す必要があります。理由も非常に簡単で、同期メソッドでのみ呼び出されます。または block 現在のスレッドのみがロックを占有し、ロックを解放できます。同様に、ロックを解放するには、このペアのメソッドを呼び出すオブジェクトのロックが現在のスレッドによって所有されている必要があります。したがって、メソッド呼び出しのペアは、ロックされたオブジェクトがメソッドのペアを呼び出すオブジェクトである同期されたメソッドまたはブロックに配置する必要があります。この条件が満たされない場合、プログラムはコンパイルできますが、実行時に IllegalMonitorStateException 例外が発生します。
wait() メソッドと Notify() メソッドの上記の特性により、これらのメソッドは同期メソッドまたは同期ブロックと一緒に使用されることが多いため、オペレーティング システムのプロセス間通信メカニズムと比較すると、それらの類似点が明らかになります。同期メソッドまたは同期ブロックは、同様の機能を提供します。オペレーティング システムのプリミティブの機能に対して、その実行はマルチスレッド メカニズムによって干渉されません。また、この対応するものは、ブロック プリミティブとウェイクアップ プリミティブと同等です (このメソッドのペアは両方とも同期化されていると宣言されています)。これらを組み合わせることで、一連の絶妙なプロセス間通信アルゴリズム (セマフォ アルゴリズムなど) をオペレーティング システム上に実装することができ、さまざまな複雑なスレッド間通信の問題を解決するために使用できます。
wait() メソッドとnotify() メソッドに関する最後の 2 つのポイント:
まず、notify() メソッドの呼び出しによってブロック解除されたスレッドは、オブジェクトの wait() メソッドの呼び出しによってブロックされたスレッドからランダムに選択されるため、プログラミングするときは特に注意してください。この不確実性から生じる問題。
2 番目: Notice() に加えて、同様の役割を果たすメソッド NoticeAll() もあります。唯一の違いは、notifyAll() メソッドを呼び出すと、そのメソッドの wait() メソッドを呼び出すことによってブロックされたすべてのスレッドが削除されることです。すべてのブロックを一度に解除します。もちろん、ロックを取得したスレッドのみが実行可能状態に入ることができます。
ブロッキングについて話すときは、デッドロックについても話さなければなりません。簡単に分析すると、タイムアウト期間を指定しない stop() メソッドと wait() メソッドの呼び出しの両方がデッドロックを引き起こす可能性があることがわかります。残念ながら、Java は言語レベルでのデッドロック回避をサポートしていないため、プログラミングではデッドロックを回避するように注意する必要があります。
上記では、Java でのスレッド ブロックのさまざまなメソッドを分析しました。wait() メソッドと Notify() メソッドは最も強力で、最も柔軟に使用できるため、これらのメソッドに焦点を当てました。ただし、これは効率の低下にもつながります。エラーが発生しやすくなります。実際の運用では、目的をより良く達成するために、さまざまな方法を柔軟に使用する必要があります。
デーモンスレッド
デーモン スレッドは特殊なタイプのスレッドであり、通常のスレッドとの違いは、アプリケーションの非デーモン スレッドがすべて終了すると、デーモン スレッドがまだ実行されている場合でもアプリケーションが終了するという点です。一方、アプリケーションは、デーモン以外のスレッドが実行されている限り終了しません。デーモン スレッドは通常、バックグラウンドで他のスレッドにサービスを提供するために使用されます。
isDaemon() メソッドを呼び出してスレッドがデーモン スレッドであるかどうかを判断したり、setDaemon() メソッドを呼び出してスレッドをデーモン スレッドとして設定したりできます。
スレッドグループ
スレッド グループは Java に固有の概念です。Java では、スレッド グループは ThreadGroup クラスのオブジェクトです。このスレッド グループはスレッドの作成時に指定され、スレッドのライフサイクル全体にわたって使用することはできません。スレッドを変更します。 ThreadGroup 型パラメーターを含む Thread クラス コンストラクターを呼び出すことで、スレッドが属するスレッド グループを指定できます。指定しない場合、スレッドはデフォルトで system という名前のシステム スレッド グループになります。
Java では、事前構築されたシステム スレッド グループを除くすべてのスレッド グループを明示的に作成する必要があります。 Java では、システム スレッド グループを除く各スレッド グループは、スレッド グループの作成時に所属するスレッド グループを指定できます。指定しない場合は、デフォルトでシステム スレッド グループに属します。このようにして、すべてのスレッド グループは、システム スレッド グループをルートとするツリーを形成します。
Java では、スレッド グループ内のすべてのスレッドを同時に操作できます。たとえば、スレッド グループの対応するメソッドを呼び出すことで、その中のすべてのスレッドの優先順位を設定したり、すべてのスレッドを開始またはブロックしたりすることもできます。それ。
Java のスレッド グループ メカニズムのもう 1 つの重要な役割は、スレッドの安全性です。スレッド グループ メカニズムを使用すると、グループ化を通じて異なるセキュリティ特性を持つスレッドを区別し、異なるグループ内のスレッドを異なる方法で処理し、スレッド グループの階層構造を通じて不平等なセキュリティ対策の採用をサポートできます。 Java の ThreadGroup クラスは、スレッド グループ ツリー内の各スレッド グループおよびスレッド グループ内の各スレッドの操作を容易にする多数のメソッドを提供します。
スレッドの状態 特定の時点で、スレッドは 1 つの状態のみを取ることができます。
新しい
まだ開始されていないスレッドはこの状態になります。
実行可能
Java 仮想マシンで実行中のスレッドはこの状態になります。
ブロックされました
ブロックされてモニター ロックを待機しているスレッドは、この状態になります。
待っている
別のスレッドが特定の操作を実行するのを無限に待機するスレッドは、この状態になります。
待機中のスレッドのステータス。スレッドは次のいずれかのメソッドを呼び出したため、待機状態になっています。
タイムアウト値なしの Object.wait
タイムアウト値なしの Thread.join
ロックサポート.パーク
待機状態のスレッドは、別のスレッドが特定の操作を実行するのを待っています。 たとえば、オブジェクトに対して Object.wait() を呼び出したスレッドは、別のスレッドがそのオブジェクトに対して Object.notify() または Object.notifyAll() を呼び出すのを待機しています。 Thread.join() を呼び出したスレッドは、指定されたスレッドが終了するのを待っています。
TIMED_WAITING
指定された待機時間に依存する操作を別のスレッドが実行するのを待機しているスレッドは、この状態になります。
指定された待機時間を持つ待機スレッドのスレッド状態。スレッドは、指定された正の待機時間を使用して次のいずれかのメソッドを呼び出すことによって、時間指定された待機状態になります。
スレッド.スリープ
タイムアウト値を指定した Object.wait
タイムアウト値を指定した Thread.join
LockSupport.parkNanos
LockSupport.parkUntil
終了しました
終了したスレッドはこの状態になります。