シリーズ記事の目次
- 2024 Java インタビュー (1) – 春の章
- 2024 Java インタビュー (2) – 春の章
- 2024 Java インタビュー (3) – 春の章
- 2024 Java インタビュー (4) – 春の章
記事ディレクトリ
スレッドのスケジューリング
スレッドの 5 つの状態
スレッドのステータス: 作成、準備完了、実行中、ブロック、デッド
1. 新しい状態 (New): スレッド オブジェクトが作成されると、新しい状態になります。たとえば、スレッド thread = new Thread() です。
2. 準備完了状態 (実行可能): 「実行可能状態」とも呼ばれます。スレッド オブジェクトが作成された後、他のスレッドはオブジェクトの start() メソッドを呼び出してスレッドを開始します。たとえば、thread.start() です。準備完了状態のスレッドは、いつでも CPU によって実行されるようにスケジュールできます。
3. 実行状態 (実行中): スレッドは実行のための CPU 権限を取得します。スレッドは準備完了状態からのみ実行状態に入ることができることに注意してください。
4. ブロック済み: ブロックされた状態は、スレッドが何らかの理由で CPU の使用権を放棄し、一時的に実行を停止することを意味します。スレッドが準備完了状態になるまで、実行状態に移行する可能性があります。ブロッキング状況には 3 つのタイプがあります。
(1) ブロッキングの待機: 実行中のスレッドは wait メソッドを実行し、スレッドは占有されているすべてのリソースを解放し、JVM はスレッドを「待機プール」に入れます。この状態になった後は、自動的にウェイクアップすることはできません。ウェイクアップするには、他のスレッドに依存して、notify または NoticeAll メソッドを呼び出す必要があります。wait はオブジェクト クラスのメソッドです (2)。 同期ブロッキング: 実行中のスレッドが同期を取得した
とき同期ロックが別のスレッドによって占有されている場合、VM はそのスレッドを「ロック プール」に入れます。
(3) その他のブロック: 実行中のスレッドがスリープまたは結合メソッドを実行するか、I/O 要求を発行すると、JVM はスレッドをブロック状態にします。スリープ状態がタイムアウトになるか、参加待機スレッドが終了またはタイムアウトになるか、I/O 処理が完了すると、スレッドは準備完了状態に戻ります。sleep は Thread クラスのメソッドです
5. 死亡状態 (Dead): スレッドは実行を終了するか、例外により run() メソッドを終了し、スレッドはライフ サイクルを終了します。
スレッドステータスの切り替え
方法 | 効果 | 違い |
---|---|---|
始める | スレッドを開始すると、仮想マシンが自動的に run() メソッドをスケジュールして実行します。 | スレッドは準備完了状態です |
走る | スレッドロジックコードブロック処理、JVMスケジューリング実行 | スレッドが実行中です |
寝る | 現在実行中のスレッドをスリープ状態にします (実行を一時停止します)。 | ロックを解除しないでください |
待って | 現在のスレッドを待機させる | 同期ロックを解除する |
通知する | このオブジェクト モニターを待機している単一のスレッドを起動します | 単一スレッドを起動する |
すべてに通知する | このオブジェクトモニターを待機しているすべてのスレッドを起動します | 複数のスレッドを起動する |
怒った | 現在のスレッドを停止し、同じ優先順位のスレッドを実行します。 | Threadクラスを使用して呼び出される |
参加する | 現在のスレッドを停止し、join メソッドを呼び出す別のスレッドが終了するまで待機します。 | スレッドオブジェクトを使った呼び出し |
yield()の実行後、スレッドは直接準備完了状態となり、即座に CPU の実行権を解放しますが、CPU の実行資格は保持されたままとなるため、次回 CPU がスレッドスケジューリングを実行するときに、スレッドは実行権を取得し、実行を継続します。
join() が実行されると、スレッドはブロッキング状態に入ります。たとえば、スレッド A の join() がスレッド B で呼び出された場合、スレッド A が終了するかスレッドが中断されるまで、スレッド B はブロッキング キューに入ります。
待つことと寝ることの違い
- wait メソッドは同期された保護されたコードで使用する必要がありますが、sleep メソッドにはこの要件がありません。
- wait メソッドは積極的にロックを解除しますが、同期コード内で sleep メソッドが実行された場合はロックは解除されません。
- wait メソッドは、再開する前に中断またはウェイクアップされるまで永久に待機することを意味します。積極的に再開されません。時間は sleep メソッドで定義され、時間が経過すると自動的に再開されます。
- wait/notifyはObjectクラスのメソッド、sleepはThreadクラスのメソッドです。
プロセスとスレッドの違い
1. 基本的な違い: プロセスはオペレーティング システムによるリソース割り当ての最小単位であり、スレッドはオペレーティング システムによる操作スケジューリングの最小単位です。
2. 所属関係が異なります。プロセスにはスレッドが含まれており、スレッドはプロセスに属します。
3. コストが異なります。プロセスの作成、破棄、切り替えのコストは、スレッドのコストよりもはるかに大きくなります。
4. 異なるリソースの所有: 各プロセスは独自のメモリとリソースを持ち、プロセス内のスレッドはこれらのメモリとリソースを共有します。
5. さまざまな制御機能と影響力: プロセスがクラッシュしても、保護モードでは他のプロセスには影響しませんが、スレッドがクラッシュするとプロセス全体が停止します。したがって、マルチプロセスはマルチスレッドよりも堅牢です。
6. CPU 使用率の違い: プロセスの CPU 使用率は、コンテキスト切り替えのオーバーヘッドが大きいため低くなりますが、スレッドの CPU 使用率は高く、コンテキストの切り替えが高速です。
7. さまざまなオペレーター: プロセスのオペレーターは通常、オペレーティング・システムであり、スレッドのオペレーターは通常、プログラマーです。
マルチスレッドを実装する 4 つの方法
Thread クラスを継承してマルチスレッドを実装する
継承クラス Thread はマルチスレッドをサポートする関数クラスであり、サブクラスを作成することでマルチスレッドのサポートを実現できます。
すべての Java プログラムの開始点は main メソッドであるため、スレッドには独自の開始点が必要であり、この開始点は run メソッドです。Thread の run メソッドはマルチスレッドの各メイン クラスで書き換える必要があるためです。
この run メソッドには戻り値がありません。つまり、スレッドが開始されると実行は継続され、コンテンツを返すことはできません。
マルチスレッドを起動するには Thread の start メソッドを呼び出すしかありませんが、run メソッドを呼び出した場合は通常の run メソッドの呼び出しとなります(このメソッドを呼び出すと run メソッド本体が実行されます)。
概要: Thread クラスの start メソッドを使用すると、マルチスレッド実行コードが開始されるだけでなく、さまざまなオペレーティング システムからリソースが割り当てられます。
ステップ:
1. Thread クラスを継承するサブクラスを作成します。
2. Thread クラスの run() を書き換えます --> run() でこのスレッドが実行する操作を宣言します
3. Threadクラスのサブクラスのオブジェクトを作成する
4. このオブジェクトを通じて start() を呼び出します。 start() 関数は、① 現在のスレッドを開始します。 ② 現在のスレッドの run() を呼び出します。
Thread クラスを継承します(Java は多重継承をサポートしていません)。
public class ExtendsThread extends Thread {
@Override
public void run() {
System.out.println('用Thread类实现线程');}
}
ランナブルの実装 (推奨)
Java には単一継承の制限があります。すべての Java プログラムはクラスの継承を回避する必要があり、スレッドにも同じことが当てはまります。単一継承の制限を解決するために、Runnable インターフェイスが作成されます。
使用法: クラスに Runnable インターフェイスを実装させるだけで、 run() メソッドをオーバーライドする必要もあります。
質問: しかし、このインターフェイスには run メソッドのみがあり、start メソッドはありません。マルチスレッドを開始するにはどうすればよいですか?
いずれにしても、マルチスレッドを開始したい場合は、Thread クラスに依存する必要があります。Thread クラスのパラメータは、Runnable パラメータのコンストラクタ メソッドです。
スレッド(Runnableターゲット)はRunnableインターフェースを受け取ります
Runnable 実装クラスをパラメータとする Thread クラスを作成し、start メソッドを呼び出してそれを開始できます。
概要: Runnable インターフェイスを実装してマルチスレッド ビジネス クラスを作成し、Thread を使用してマルチスレッドを開始します。
Runnable インターフェイスを実装する (推奨)
public class RunnableThread implements Runnable {
@Override
public void run() {
System.out.println('用实现Runnable接口实现线程');}
}
Callableインターフェイスを実装する
Callable インターフェイスを実装します (例外をスローできる戻り値を備えています)。
ステップ:
1. Callableインターフェイスを実装する
2. 内部の Call メソッドを書き換えます (Run ではなく Call であることに注意してください)。
3. Callable実装クラスのオブジェクトを作成する
4. 実装クラス オブジェクトをパラメータとして FutureTask コンストラクターに渡します。
5. FutureTask オブジェクトをパラメータとして Thread コンストラクターに渡します (FutureTask は Runnable インターフェイスを実装しているため、このように渡すことができます)
6. Threadクラスのstartメソッドを呼び出す
//class CallableTask implements Callable<Integer> {
//@Override
//public Integer call() throws Exception { return new Random().nextInt();}
//}
@Override
public Object call() throws Exception {
System.out.println("CallableImpl");
return "我是Call方法的返回值";
}
public static void main(String[] args) {
CallableImpl callable=new CallableImpl();
FutureTask<Object> futureTask=new FutureTask<>(callable);
Thread thread=new Thread(futureTask);
需要注意一件事:
FutureTask类中的get方法获取返回值只能执行一次
而且,如果使用了这个方法但是线程还没有运行到可以返回的那行代码,那么就会一直阻塞
比如如果我在这里执行了如下代码:
Object result=futureTask.get();
那么就永远阻塞了
当然,我更想说的是,如果你使用的是这种方法创建线程并且需要返回值的话,里面就别写死循环
否则就是死锁在召唤
thread.start();
try {
Object result=futureTask.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
Callable インターフェイスを実装してマルチスレッドを作成する方が、Runnable インターフェイスを実装してマルチスレッドを作成するよりも強力であることをどのように理解すればよいでしょうか?
1.call() は値を返すことができます。
2.call() は例外をスローし、外部操作によってキャプチャされて例外情報を取得できます。
3.Callable はジェネリックスをサポートします