JAVAマルチスレッド同時実行の超詳細な説明(終了するために、次の記事にあります)
1.JAVAマルチスレッド同時実行
1.1.1.JAVAコンカレントナレッジベース
1.1.2.JAVAスレッドの実装/作成方法
1.1.2.1。Threadクラスを継承します
Threadクラスは基本的に、スレッドのインスタンスを表すRunnableインターフェイスのインスタンスを実装します。スレッドを開始する唯一の方法は、Threadクラスのstart()インスタンスメソッドを使用することです。start()メソッドはネイティブメソッドであり、新しいスレッドを開始してrun()メソッドを実行します。
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
myThread1.start();
1.1.2.2。Runnableインターフェイスを実装します。
自分のクラスがすでに別のクラスを拡張している場合、Threadを直接拡張することはできません。この場合、Runnableインターフェイスを実装できます。
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
//启动 MyThread,需要首先实例化一个 Thread,并传入自己的 MyThread 实例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
//事实上,当传入一个 Runnable target 参数给 Thread 后,Thread 的 run()方法就会调用
target.run()
public void run() {
if (target != null) {
target.run();
}
}
1.1.2.3。ExecutorService、Callable、Futureには戻り値スレッドがあります
戻り値のあるタスクはCallableインターフェースを実装する必要があります。同様に、戻り値のないタスクはRunnableインターフェースを実装する必要があります。Callableタスクの実行後、Futureオブジェクトを取得し、オブジェクトでgetを呼び出すことにより、Callableタスクによって返されるオブジェクトを取得できます。スレッドプールインターフェイスExecutorServiceと組み合わせることで、結果が返される伝説的なマルチスレッドを実現できます。
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 执行任务并获取 Future 对象
Future f = pool.submit(c);
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从 Future 对象上获取任务的返回值,并输出到控制台
System.out.println("res:" + f.get().toString());
}
1.1.2.4。スレッドプールベースのアプローチ
スレッドとデータベース接続は非常に貴重なリソースです。その場合、必要になるたびに作成し、不要なときに破棄するのはリソースの無駄です。次に、キャッシュ戦略を使用できます。つまり、スレッドプールを使用できます。
// 创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true) {
threadPool.execute(new Runnable() {
// 提交多个线程任务,并执行
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running ..");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
1.1.3スレッドプール
Javaのスレッドプールの最上位インターフェイスはExecutorですが、厳密な意味では、Executorはスレッドプールではなく、スレッドを実行するためのツールです。実際のスレッドプールインターフェイスはExecutorServiceです。
1.1.3.1。newCachedThreadPool
必要に応じて新しいスレッドを作成できるスレッドプールを作成しますが、使用可能な場合は以前に作成したスレッドを再利用します。多くの短期間の非同期タスクを実行するプログラムの場合、これらのスレッドプールはプログラムのパフォーマンスを向上させることがよくあります。executeを呼び出すと、以前に作成されたスレッドが再利用されます(スレッドが使用可能な場合)。使用可能な既存のスレッドがない場合は、新しいスレッドが作成され、プールに追加されます。60秒間使用されなかったスレッドを終了し、キャッシュから削除します。したがって、長時間アイドル状態が続くスレッドプールは、リソースを使用しません
。
1.1.3.2。newFixedThreadPool
固定数のスレッドで再利用可能なスレッドプールを作成し、これらのスレッドを共有の無制限キューで実行します。どの時点でも、ほとんどのnThreadsスレッドはタスクの処理でアクティブになります。すべてのスレッドがアクティブなときに追加のタスクが送信された場合、追加のタスクは、使用可能なスレッドが存在する前にキューで待機します。シャットダウン前の実行中に障害が発生したためにスレッドが終了した場合、新しいスレッドがそのスレッドを置き換えて、後続のタスクを実行します(必要な場合)。スレッドが明示的にシャットダウンされるまで、
プール内のスレッドは常に存在します。
1.1.3.3。newScheduledThreadPool
コマンドを特定の遅延後に実行するようにスケジュールしたり、定期的に実行したりできるスレッドプールを作成します。
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3);
scheduledThreadPool.schedule(newRunnable(){
@Override
public void run() {
System.out.println("延迟三秒");
}
}, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(newRunnable(){
@Override
public void run() {
System.out.println("延迟 1 秒后每三秒执行一次");
}
},1,3,TimeUnit.SECONDS);
1.1.3.4。newSingleThreadExecutor
Executors.newSingleThreadExecutor()はスレッドプールを返します(スレッドプールにはスレッドが1つしかない)。このスレッドプールは、スレッドが終了した後(または例外が発生したとき)にスレッドを再開して、元のスレッドを置き換えて実行を続行できます。
1.1.4。スレッドのライフサイクル(状態)
スレッドが作成されて開始されると、スレッドは開始されるとすぐに実行状態になることも、常に実行状態になることもありません。
スレッドのライフサイクルでは、新規、実行可能、実行中、ブロック済み、およびデッドの5つの状態を経る必要があります。特にスレッドが開始されると、常にCPUを「占有」して単独で実行できるとは限らないため、CPUは複数のスレッドを切り替える必要があるため、スレッドの状態は実行とブロックを複数回切り替えます。
1.1.4.1。新しい状態(NEW)
プログラムがnewキーワードを使用してスレッドを作成すると、スレッドは新しく作成された状態になります。この時点では、JVMのみがメモリを割り当て、メンバー変数の値を初期化します。
1.1.4.2。準備完了状態(実行可能):
スレッドオブジェクトがstart()メソッドを呼び出すと、スレッドは準備完了状態になります。Java仮想マシンは、メソッド呼び出しスタックとそのプログラムカウンターを作成し、スケジュールされた操作を待ちます。
1.1.4.3。実行中:
準備完了状態のスレッドがCPUを取得し、run()メソッドのスレッド実行本体の実行を開始すると、スレッドは実行状態になります。
1.1.4.4。ブロック状態(BLOCKED):
ブロッキング状態は、スレッドが何らかの理由でcpuを使用する権利を放棄したこと、つまりcpuタイムスライスを放棄して一時的に実行を停止したことを意味します。
スレッドが実行可能状態になるまで、cpuタイムスライスを再度取得して実行状態に移行する機会があります。ブロッキングには3つのタイプがあります:
1。ブロッキングを待機しています(o.wait->キューを待機しています):実行中のスレッドはo.wait()メソッドを実行し、JVMはスレッドを待機キューに入れます。
2.2。同期ブロッキング(ロック->ロックプール):実行中のスレッドがオブジェクトの同期ロックを取得するときに、同期ロックが別のスレッドによって占有されている場合、JVMはそのスレッドをロックプールに配置します。
3.3。その他のブロッキング(スリープ/参加):実行中のスレッドがThread.sleep(long ms)またはt.join()メソッドを実行するとき、またはI / O要求が発行されると、JVMはスレッドをブロック状態にします。sleep()状態がタイムアウトするか、join()がスレッドの終了またはタイムアウトを待機するか、I / O処理が完了すると、スレッドは再び実行可能状態になります。
1.1.4.5。スレッドデス(DEAD)
スレッドは次の3つの方法で終了し、その後停止します。
ノーマルエンド:run()またはcall()メソッドが実行され、スレッドは正常に終了します。
異常終了:スレッドはキャッチされない例外またはエラーをスローします。
コールストップ:スレッドのstop()メソッドを直接呼び出して、スレッドを終了します。このメソッドは通常、デッドロックが発生しやすいため、お勧めしません。
1.1.5。スレッドを終了する4つの方法
1.1.5.1。通常の操作の終了
プログラムが終了すると、スレッドは自動的に終了します。
1.1.5.2。終了フラグを使用してスレッドを終了します
通常、スレッドはrun()メソッドの実行後に正常に終了しますが、一部のスレッドはサーボスレッドであることがよくあります。それらは長時間実行する必要があり、これらのスレッドは特定の外部条件が満たされた場合にのみシャットダウンできます。変数を使用してループを制御します。たとえば、
最も直接的な方法は、ブール型フラグを設定し、このフラグをtrueまたはfalseに設定し
てwhileループを終了するかどうかを制御することです。コード例:
public class ThreadSafe extends Thread {
public volatile boolean exit = false;
public void run() {
while (!exit){
//嗡嗡嗡
do something
}
}
}
終了フラグが定義されています。exitがtrueの場合、whileループが終了します。exitのデフォルト値はfalseです。exitが定義されている場合、Javaキーワードvolatileが使用されます。このキーワードの目的は、exitを同期することです。同時にexitの値を変更できるのは1つのスレッドだけです。
1.1.5.3.Interruptメソッドはスレッドを終了します
interrupt()メソッドを使用してスレッドを中断する状況は2つあります。
- スレッドはブロックされた状態にあります。ソケット内のスリープ、同期ロック待機、レシーバー、受け入れ、その他のメソッドなど、スレッドはブロックされた状態になります。スレッドのinterrupt()メソッドが呼び出されると、InterruptExceptionがスローされます。ブロッキングメソッドは、この例外をスローし、コードを介して例外をキャッチしてから、ループ状態から抜け出し、このスレッドの実行を終了する機会を与えます。通常、多くの人は、割り込みメソッドが呼び出されている限り、スレッドは終了すると考えていますが、これは実際には
間違っています。最初にInterruptedExceptionをキャッチしてから、ループを中断して実行メソッドを正常に終了する必要があります。 - スレッドはブロック状態ではありません。isInterrupted()を使用して、ループを終了するスレッドの割り込みフラグを決定します。interrupt()メソッドを使用すると、割り込みフラグがtrueに設定されます。これは、カスタムフラグを使用してループを制御するのと同じです。
public class ThreadSafe extends Thread {
public void run() {
while (!isInterrupted()){
//非阻塞过程中通过判断中断标志来退出
try{
Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出
}catch(InterruptedException e){
e.printStackTrace();
break;//捕获到异常之后,执行 break 跳出循环
}
}
}
}
1.1.5.4。stopメソッドはスレッドを終了します(スレッドは安全ではありません)
プログラムでthread.stop()を直接使用してスレッドを強制終了することもできますが、stopメソッドは非常に危険です。通常のプログラムでコンピュータをシャットダウンするのではなく、突然コンピュータの電源を切るようなものです。予期しない結果が生じる可能性があります。安全でないのは主に:
thread.stop()が呼び出された後、子スレッドを作成したスレッドはThreadDeatherrorエラーをスローし、子スレッドによって保持されているすべてのロックを解放します。一般に、ロックのコードブロックは、データの整合性を保護するためのものです。thread.stop()を呼び出すと、スレッドが保持しているすべてのロックが突然解放され(制御不能)、保護されたデータは次のようになります。不整合がある可能性があり、破損したデータを使用すると、他のスレッドによって非常に奇妙なアプリケーションエラーが発生する可能性があります。
このため、スレッドを終了するためにstopメソッドを使用することはお勧めしません。
1.1.6。睡眠と待機の違い
- sleep()メソッドの場合、最初にメソッドがThreadクラスに属していることを知る必要があります。wait()メソッドはObjectクラスに属しています。
- sleep()メソッドを使用すると、プログラムは指定された時間実行を一時停止し、cpuの他のスレッドを放棄しますが、監視状態は維持され、指定された時間が来ると自動的に実行状態に戻ります。
- sleep()メソッドを呼び出すプロセスでは、スレッドはオブジェクトロックを解放しません。
- wait()メソッドが呼び出されると、スレッドはオブジェクトロックを放棄し、このオブジェクトを待機している待機ロックプールに入ります。このオブジェクトのnotify()メソッドを呼び出した後、スレッドはオブジェクトロックプールに入り、オブジェクトロックを取得して実行状態に入る準備をします。
1.1.7。開始と実行の違い
- start()メソッドはスレッドを開始します。これにより、マルチスレッド操作が実際に実現されます。このとき、runメソッド本体コードの実行が完了するのを待つ必要はなく、以下のコードを直接実行し続けることができます。
- Threadクラスのstart()メソッドを呼び出してスレッドを開始します。この時点で、スレッドは準備完了状態にあり、実行されていません。
- run()メソッドはスレッド本体と呼ばれ、実行されるスレッドの内容が含まれています。スレッドは実行状態に入り、run関数でコードの実行を開始します。Runメソッドが終了し、スレッドが終了します。次に、CPUは他のスレッドをスケジュールします。
1.1.8.JAVAバックグラウンドスレッド
- 定義:デーモンスレッド-「サービススレッド」とも呼ばれ、バックグラウンドスレッドであり、ユーザースレッドにパブリックサービスを提供する機能があり、提供するユーザースレッドがない場合、自動的に終了します。
- 優先度:デーモンスレッドの優先度は比較的低く、システム内の他のオブジェクトやスレッドにサービスを提供するために使用されます。
- 設定:setDaemon(true)を使用してスレッドを「デーモンスレッド」として設定します。ユーザースレッドをデーモンスレッドとして設定する方法は、スレッドオブジェクトを作成する前にスレッドオブジェクトのsetDaemonメソッドを使用することです。
- デーモンスレッドで生成された新しいスレッドもデーモンです。
- スレッドはJVMレベルです。例としてTomcatを取り上げます。Webアプリケーションでスレッドを開始すると、このスレッドのライフサイクルはWebアプリケーションと同期されません。つまり、Webアプリケーションを停止しても、このスレッドはアクティブなままです。
- 例:ガベージコレクションスレッドは従来のデーモンスレッドです。プログラムに実行中のスレッドがなくなると、プログラムはガベージを生成しなくなり、ガベージコレクターは何もしなくなります。したがって、ガベージコレクションスレッドがJVMの場合ガベージコレクションスレッドは、スレッドが残っているときに自動的に終了します。システム内のリサイクル可能なリソースをリアルタイムで監視および管理するために、常に低レベルの状態で実行されます。
- ライフサイクル:デーモンは、バックグラウンドで実行される特別なプロセスです。制御端末から独立しており、定期的に特定のタスクを実行するか、特定のイベントが発生するのを待ちます。つまり、デーモンスレッドは端末に依存せず、システムに依存し、システムとともに「存続」します。JVM内のすべてのスレッドがデーモンスレッドである場合、JVMは終了できます。デーモン以外のスレッドが1つ以上ある場合、JVMは終了しません。