マルチスレッド化
1. マルチスレッドを理解する
マルチプロセス: 各プログラムはプロセスです。オペレーティング システムは同時に複数のプログラムを実行できます。マルチプロセスの目的は、CPU リソースを効率的に使用することです。プロセスが開かれるたびに、システムはシステム リソースを割り当てる必要があります (メモリ リソース)がこのプロセスに必要になります。
マルチスレッド: スレッドはプロセス内の小さな実行単位です。各スレッドはタスクを完了します。各プロセスには複数のスレッドが含まれます。各スレッドは独自の処理を実行します。プロセス内のすべてのスレッドはプログラム リソースを共有します。
メインスレッド: プロセス内に少なくとも 1 つのメインスレッドがあり、他のサブスレッドはメインスレッドによって開始されます。メインスレッドは必ずしも他のスレッドの終了後に終了するわけではなく、他のスレッドが終了する前に終了する場合があります。 JAVAがメインの機能です
同時実行性: プリエンプティブな方法で CPU をめぐって競合する複数のプログラムを指します。
並列性: 複数のプログラムが同時に実行されます。
同期: 1 つのプログラムが別のプロセスを実行できる
2. Java はマルチスレッド操作を実装します。
2.1 Thread クラスを継承してマルチスレッドを実装する
java.lang.Threadクラスを継承すると、このクラスはスレッドクラスになります このクラスは独立した実行単位になります スレッドのコードはメソッド内に記述する必要があります メソッドはThreadクラスで定義します 記述するスレッドは書き換える必要が
run()
ありrun()
ますrun()
その方法です。
run()
このメソッドは手動呼び出しができないスレッドメソッドであり、手動で呼び出した
場合はrun()
スレッド内で呼び出されるメソッドとなり、呼び出し元のスレッドによって自動的に呼び出されます。スレッドを開始するには、
start()
メソッドを呼び出す必要があります。これにより、新しいスレッドが自動的に呼び出され、run メソッドのコンテンツが自動的に実行されます。
スレッドクラス
/**
* 当一个类继承了Thread类,该类就成了一个线程类
* 该类必须重写Thread中的run()方法
*/
public class Demo01 extends Thread{
/**
* run方法不允许手动调用
* 当开启一个线程后,该方法会自动执行
*/
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"0:"+i);
}
}
}
主要
public class Test {
public static void main(String[] args) {
Demo01 demo01=new Demo01();
//run方法为线程方法,该方法不允许手动调用
//如果手动调用run()方法就成了线程内部调用一个方法,手动调用该方法就会被自动归入到调用线程
// demo01.run();
//开启线程,开启后自动执行run()方法
demo01.start();
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"1:"+i);
}
}
}
2.2 マルチスレッドを実現するための Runable インターフェイスの実装
クラスが Runable インターフェイスを実装する場合、そのクラスはスレッド タスク クラスになります。
スレッドタスククラスは、スレッドによって実行されるタスクのみを定義します。
スレッドタスクは、実行のためにスレッドタスクをスレッドオブジェクトに送信する必要があります
/**
* 当一个类实现了Runable接口该类就是一个线程任务类
* 线程任务类只定义线程要执行的任务
* 线程任务需要将线程任务提交给线程对象执行
*/
public class Demo01 implements Runnable {
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"1:"+i);
}
}
public class Test {
public static void main(String[] args) {
//创建线程任务对象
Demo01 demo01=new Demo01();
//将线程任务对象交给线程对象,并启动线程
new Thread(demo01).start();
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"1:"+i);
}
}
}
Runable インターフェースの実装は通常、このように使用されます。
/**
* 一般使用
*/
public class Test01 {
public static void main(String[] args) {
//创建一个线程类对象并启动线程
new Thread(() -> {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
},"T0").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
},"T1").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
},"T2").start();
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"1:"+i);
}
}
}
2.3 Callableインターフェースの実装によるマルチスレッドの実現
Callable と Runable の両方を使用してスレッド タスクを作成します
違い
① Runnable はメソッドを通じて
run()
スレッドタスクを作成するため、メソッド実行後に結果を返すことができないため、結果を取得する必要がある場合はグローバル変数を宣言して取得する必要があります。Callable は
call()
、実行後にジェネリック型で指定された型の結果を直接返すことができるメソッドを通じてスレッド タスクを作成します。②Runnableのメソッドは
run()
例外をスローしないCallable のメソッドは
call()
例外をスローする可能性があります③RunnableはThreadオブジェクトを通じて直接スレッドタスクを実行できる
Callable はスレッド タスクを定義し、スレッド タスクを実行するには FutureTask (将来のタスク クラス) または ExecutorService を必要とします。
FutureTask クラス
-
FutureTask は、Future インターフェイスによって実装されたクラスです。
-
Future は、非同期計算の将来の結果 (処理 | スレッドの制御) を表します。スレッドの処理が完了した後、結果は最終的に Future に格納されます。Future インターフェイスは、長時間実行されるメソッドの非同期処理に最適です。これにより、Future によってカプセル化されたタスクが完了するのを待ちながら、他の作業を実行できるようになります。
-
Future では 5 つのメソッドが提供されます
①
cancel(boolean mayInterruptIfRunning)
: スレッドの実行を停止するために使用され、タスクが正常に停止できた場合は true を返し、タスクが完了または停止できなかった場合は false を返します、②: タスクがキャンセルされたかどうかを判断し、完了前にキャンセルされた場合
はisCancelled()
return true、Callable thread task、FutureTask タスクに送信されますが、スレッドの開始と実行は依然として Thread オブジェクトに依存します。
③get()
: スレッド タスクの実行結果 (Callable タスクの場合) を取得します。完了していない場合は、ブロック状態;
スレッドがデッド状態に入る
④get(long timeout,TimeUnit unit)
: 指定時間内にタスクの結果を取得する ;
⑤isDone()
: タスクが完了したかどうかを判定し、完了した場合は true を返す。/** * 线程任务类 */ public class Demo01 implements Callable<Integer> { @Override public Integer call() throws Exception { int sum=0; for (int i=0;i<100;i++){ sum+=i; System.out.println(Thread.currentThread().getName()+":"+i); Thread.sleep(1000); } return sum; } }
public class Demo01Test { public static void main(String[] args) throws InterruptedException { //创建demo01对象 Demo01 demo01=new Demo01(); //创建FutureTask对象,并将callableTask对象传入 FutureTask futureTask=new FutureTask<>(demo01); //执行线程任务 Thread t1=new Thread(futureTask); t1.start();//启动现场 /** * 循环输出主线程内容,并在适合位置取消线程任务 */ for (int i=0;i<100;i++){ if (i==20){ futureTask.cancel(true);//取消线程任务 break; } System.out.println(Thread.currentThread().getName()+":"+i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } if (futureTask.isCancelled()){ //判断线程是否取消 System.out.println("任务已经取消..."); } while (!futureTask.isDone()){ System.out.println("任务执行完成..."); try { //获得线程任务执行结果,未完成进入堵塞状态 System.out.println("结果为:"+futureTask.get()); } catch (Exception e) { e.printStackTrace(); } System.out.println("任务结束..."); Thread.sleep(100); } } }
-
PS: スレッドをキャンセルするには、スレッド コードの実行のみが完了します。
実行終了方法:
- 正常終了:スレッド内のすべてのコードの実行が完了
- 異常終了: スレッド内のコードにスレッド内のコードを「中断」させる方法を見つける、InterruptedException (中断例外)
- FutureTaskにおけるキャンセルは、異常割り込みによるスレッドの割り込みをキャンセルすることです。
- 特定のポイントに到達したときにスレッド内のコードを終了させる
Thread クラスのメソッド:
interrupt()
割り込み信号を送信して、
isInterrupted()
現在のスレッドが割り込まれているかどうかを判断します。
2.4 スレッドプールによるマルチスレッドの実装
スレッド プールはスレッドをロードするためのコンテナです (マップ コレクションの実装)
スレッド プールが初期化されると、いくつかのスレッド (スレッド初期化サイズ <コア スレッド>) オブジェクトが自動的に作成され、使用するためにスレッド プールに入れられます。
プログラムがスレッドを使用する必要がある場合、スレッド オブジェクトを個別に作成する必要はなく、スレッド プールからアイドル状態のスレッド オブジェクトを取得して使用します。
スレッド プールにアイドル状態のスレッドがない場合、スレッド プールは、定義されたルールに従ってプログラムが使用する新しいスレッドを自動的に生成します。スレッドが実行されると、スレッド プールはスレッドをアイドル状態としてマークし、他のプログラムを待機します。使用します。
作成されたスレッド オブジェクトがスレッド プールの上限に達した場合、新しく要求されたスレッド タスクは待機キューに格納され、スレッド プール内のスレッドが空くのを待ちます。
スレッド プールにはコア スレッドと非コア スレッドがあり、コア スレッドは使用後に解放されず、非コア スレッドは使用後に使用されます。
Java は Executors クラスを通じてスレッド プール オブジェクトを取得します。
① newSingleThreadExecutor()
: シングルスレッドのスレッドオブジェクトを作成する
- スレッドが 1 つだけ含まれている
public class SingleThreadDemo01 {
public static void main(String[] args) {
/**
* 创建线程对象并返回一个ExecutorService对象
* ExecutorService用于管理线程池,并向线程池提交任务
* ExecutorService常用方法:
* ① shutdown():关闭线程池,平滑关闭,该方法执行后不再接受新线程任务,将已提交的线程任务结束后关闭线程池
* ② shutdownNow():立即关闭线程池,该方法执行后不再接受新线程任务,如果当前已提交线程任务未执行完,也立即关闭
* ③ submit():向线程池中提交线程任务,
* 当一个线程任务被提交到线程池中,线程池会找到一个空闲线程执行该任务
* 如果不存在空闲线程则等待
*/
ExecutorService executorService = Executors.newSingleThreadExecutor();
//提交线程任务
executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
return null;
}
});
executorService.shutdown();
}
}
② newFixedThreadPool(int nThreads)
: スレッド数を固定したスレッドプールオブジェクトを作成する
- スレッド プールは初期化時に一定数のスレッド オブジェクトを作成し、使用中にスレッド オブジェクトが増減することはありません。
- スレッド数が比較的固定されているシナリオに適用可能
public class FixedThreadDemo01 {
public static void main(String[] args) {
/**
* 创建线程对象并返回一个ExecutorService对象
* ExecutorService用于管理线程池,并向线程池提交任务
* ExecutorService常用方法:
* ① shutdown():关闭线程池,平滑关闭,该方法执行后不再接受新线程任务,将已提交的线程任务结束后关闭线程池
* ② shutdownNow():立即关闭线程池,该方法执行后不再接受新线程任务,如果当前已提交线程任务未执行完,也立即关闭
* ③ submit():向线程池中提交线程任务,
* 当一个线程任务被提交到线程池中,线程池会找到一个空闲线程执行该任务
* 如果不存在空闲线程则等待
*/
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
//提交线程任务
executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
return null;
}
});
}
executorService.shutdown();
}
}
③ newCachedThreadPool()
: バッファ付きスレッドプールを作成する
- スレッド プールの初期化時に一定数のスレッド オブジェクトを作成し、使用中にスレッド プール内のスレッド数を動的に増減します。
- シーンに適応 –> なし (決して使用しない)
- バッファ付きスレッド プールは、最大 21 億スレッドのスレッドをサポートします (リソースが枯渇しやすい)
public class CachedThreadDemo01 {
public static void main(String[] args) {
/**
* 创建线程对象并返回一个ExecutorService对象
* ExecutorService用于管理线程池,并向线程池提交任务
* ExecutorService常用方法:
* ① shutdown():关闭线程池,平滑关闭,该方法执行后不再接受新线程任务,将已提交的线程任务结束后关闭线程池
* ② shutdownNow():立即关闭线程池,该方法执行后不再接受新线程任务,如果当前已提交线程任务未执行完,也立即关闭
* ③ submit():向线程池中提交线程任务,
* 当一个线程任务被提交到线程池中,线程池会找到一个空闲线程执行该任务
* 如果不存在空闲线程则等待
*
* 初始化线程对象数为0
* 最大线程数:int类型最大值21亿多
*/
ExecutorService executorService = Executors.newCachedThreadPool();
//提交线程任务
for (int i = 0; i < 50; i++) {
executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return null;
}
});
}
executorService.shutdown();
}
}
④newScheduledThreadPool(int corePoolSize):
定期スレッドプールの作成
-
スレッド プールは主に定期的なタスク (タイマーと同様、一定の間隔で実行されるタスク) を実行するために使用されます。
public class ScheduledThreadDemo01 { public static void main(String[] args) { /** * 创建线程对象并返回一个ExecutorService对象 * ExecutorService用于管理线程池,并向线程池提交任务 * ExecutorService常用方法: * ① shutdown():关闭线程池,平滑关闭,该方法执行后不再接受新线程任务,将已提交的线程任务结束后关闭线程池 * ② shutdownNow():立即关闭线程池,该方法执行后不再接受新线程任务,如果当前已提交线程任务未执行完,也立即关闭 * ③ submit():向线程池中提交线程任务, * 当一个线程任务被提交到线程池中,线程池会找到一个空闲线程执行该任务 * 如果不存在空闲线程则等待 */ ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); //提交线程任务 /** * 执行周期性任务线程 * 参数1:线程任务(Runnable任务)不能执行Callable任务 * 参数2:延迟时间,负数表示不延迟 * 参数3:间隔时间量 * 参数4:时间单位,该时间单位用于设置延迟时间和间隔时间的单位 */ scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("执行周期性任务..."); } },0,1000, TimeUnit.MILLISECONDS); } }
⑤カスタムスレッドプール:推奨
public class 自定义ThreadPool {
public static void main(String[] args) {
/**
* 使用自定义线程池对象
* 参数1:初始化线程数(核心线程数,不会被销毁)
* 参数2:最大线程数
* 参数3:空闲时间,当一个非核心线程允许空闲的最大时间,如果空闲时间大于该时间则自动销毁
* 参数4:空闲时间时间单位
* 参数5:等待队列(阻塞队列)
* 参数6:设置拒绝策略,当线程池无法接受新提交线程任务时,应该采取的措施
* 拒绝策略(4种):
* new ThreadPoolExecutor.AbortPolicy()异常策略,当有新线程任务提交后会报拒绝异常(默认)
* new ThreadPoolExecutor.CallerRunsPolicy()谁提交谁执行策略,线程池已满,该策略将线 * 程给提交者执行
* new ThreadPoolExecutor.DiscardPolicy()丢弃策略,当新任务提交后,线程池无法满足,直 *接将新提交任务丢弃
* new ThreadPoolExecutor.DiscardOldestPolicy()丢弃策略,丢弃等待时间最久的任务
*/
//创建一个等待队列对象
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(2);
ExecutorService executorService = new ThreadPoolExecutor(3,
5,
1000L,
TimeUnit.MILLISECONDS, queue);
for (int i = 0; i < 5; i++) {
executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return null;
}
});
}
executorService.shutdown();
}
}
質問: スレッド プール内のスレッド数が最大スレッド数に達し、待機キューがいっぱいになった場合、スレッドを送信したい場合はどうすればよいですか?
スレッド数が最大スレッド数に達し、待機領域がいっぱいになった場合、これに対処する手段がない場合、新しいスレッドの送信により例外 RejectedExecutionException が報告されます。
この状況は、「拒否ポリシー」を設定することで対処できます。
拒否ポリシー (4 種類):
新しい ThreadPoolExecutor.AbortPolicy() 例外ポリシー、新しいスレッド タスクが送信されると、拒否例外が報告されます (デフォルト)
new ThreadPoolExecutor.CallerRunsPolicy() ポリシーを送信した人、スレッド プールがいっぱい、ポリシーは、スレッドが submitter の新しい ThreadPoolExecutor.DiscardPolicy() 破棄ポリシーを実行します
。新しいタスクが送信されると、スレッド プールはそれを満たすことができず、新しく送信されたタスクは直接破棄されます。新しい
ThreadPoolExecutor.DiscardOldestPolicy() の置き換えポリシーにより、待機時間が最も長いタスクが置き換えられます。
3 スレッド状態
3.1 オペレーティング システムがスレッド状態を定義する
-
New : Thread クラスまたはそのサブクラスのオブジェクトが作成されると、新しいスレッド オブジェクトは新しく作成された状態になります。この時点で、スレッドには対応するメモリ空間とその他のリソースがすでに存在しており、スレッドは新しく作成された状態では実行されません。
-
Ready : スレッド オブジェクトが作成されると、他のスレッドがその
start()
メソッドを呼び出し、スレッドは Ready 状態に入り、この状態のスレッドは実行可能プール (スレッド プール)に配置され、CPU を使用する権利を待ちます。 -
ブロッキング: 5 つの理由 ブロッキング: 現在のスレッドが CPU を放棄し、他のスレッドが CPU を取得できるようにします。
-
オブジェクト待機プールのブロック状態: スレッドの実行中にオブジェクトのメソッドが実行されると
wait()
、JVM はスレッドを待機プールに入れます。 -
オブジェクト ロック プールのブロック状態: スレッドが実行中にオブジェクトの同期ロックを取得しようとしているときに、オブジェクトの同期ロックが他のスレッドによって呼び出されると、JVM はそのスレッドをオブジェクト ロック プールに入れます。
-
I/Oリクエストを発行する
-
他のスレッドから
join()
メソッドを呼び出す -
実行
sleep(int millsecond)
方法
-
-
実行中: この状態のスレッドは CPU を占有し、プログラム コードを実行します。
-
Death : スレッド プログラムの実行が終了した後、この状態に入ります。
3.2 JVM によって定義されるスレッド状態
-
新しい状態(NEW): スレッド オブジェクトは作成されましたが、
start()
メソッドは実行されていません。 -
実行可能状態(RUNNABLE): JVM で実行されていますが、CPU リソースを取得していません。
-
ブロック状態 - オブジェクト ロックを待機しているブロック状態(BLOCKED): 実行中のスレッドが他のスレッドによってロックされているリソースを取得しようとすると、そのリソースはオブジェクト ロック プールに置かれます。
ブロック状態 - プール内でブロック状態を待機中(WAITTING): スレッドが
wait()
メソッドを呼び出すと、自動的にこの状態になります。ブロック状態 - 時間制限付きブロック状態(TIMED_WAITING): この状態のスレッドは、待機時間が経過すると自動的に RUNNABLE に戻ります
sleep(1000)
。wait(100)
-
終了状態TERMINATED: スレッド プログラムの実行が終了した後にこの状態に入ります。
4スレッドのスケジューリング
スレッドのスケジューリングとは、実行中のスレッドが特定の操作を通じて CPU を放棄し、ブロック状態に入ることができるようにすることを指します。
オペレーティング システムのスケジューリング: 特定のスケジューリング ルールに従ったスケジューリング、ランダム性が高すぎる、不確実な I/O 要求
-
wait()
: 待機中のスレッド (スレッドは待機プールに置かれ、ウェイクアップを待機します); -
wait(lang time)
: 制限時間待ち、制限時間内に起動しない場合は自動的に起動します -
notify()
: 待機中のスレッドを起動します -
notifyAll()
: 待機中のすべてのスレッドを起動します -
sleep()
: スレッド スリープ、指定したスレッドを一定時間 (ミリ秒単位) スリープさせた後、自動的にウェイクアップします。このメソッドが実行されると、スレッドは自動的に CPU を放棄し、準備完了状態になるまでブロック状態になります。スリープ後、CPU リソースを待ちます。public class sleepDemo { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { for (int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); try { Thread.sleep(10);//休眠10ms } catch (InterruptedException e) { throw new RuntimeException(e); } } } }).start(); for (int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
-
join()
: スレッド接続、待機スレッド、一方のスレッドで別のスレッドの join メソッドを呼び出した後、他方のスレッドの実行が終了するまでスレッドは自動的に待機状態になります。
public class joinDemo {
public static void main(String[] args) {
Thread t1=new Thread(() -> {
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t1");
Thread t2=new Thread(() -> {
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
if (i==50){
//当i=50将t2连接到t1后
try {
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t2");
t1.start();
t2.start();
}
}
-
yeild()
: スレッドの譲歩、このスレッドの実行を一時停止し、現在実行中のスレッドに CPU を放棄させ、準備完了状態に戻って CPU の占有を継続します。Thread 静的メソッド: このメソッドでは、現在のプロセスがブロックされた状態にはなりません。州public class yieldDemo { public static void main(String[] args) { new Thread(() -> { for (int i = 0; i < 1000; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); if (i>900){ Thread.yield();//线程让步 } } }, "t1").start(); new Thread(() -> { for (int i = 0; i < 1000; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } }, "t2").start(); } }
-
スレッド優先度の設定: スレッドの優先度を設定することでスレッドの実行確率を高めることができ、優先度が高いほどCPUを取得できる確率が高くなりますが、毎回CPUを取得できるとは限りません。
Java スレッド優先度レベル 1 ~ 10、10 が最高レベル、1 が最低レベル、デフォルトは 5
スレッドのsetPriorityメソッドを使用して、スレッドの優先度を設定できます。Thread クラスには、最高、最低、通常のレベルを表す 3 つのフィールド定数があります
Thread.MAX_PRIORITY
。Thread.MIN_PRIORITY
Thread.NORM_PRIORITY
public class priorityDemo { public static void main(String[] args) { Thread t1 = new Thread(() -> { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } }, "t1"); Thread t2 = new Thread(() -> { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } }, "t2"); // t1.setPriority(10); // t2.setPriority(5); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY); System.out.println("t1线程优先级"+t1.getPriority()); System.out.println("t2线程优先级"+t2.getPriority()); t1.start(); t2.start(); } }
PS: デーモンプロセス
デーモン スレッドはすべてのスレッドの中で最後に終了します。通常、デーモン スレッドは統計の監視などに使用されます。他のスレッドが実行されると、デーモン スレッドは自動的に終了します。実行されない場合は強制的に終了します。 JVM によって終了されました。
5つのスレッドを同期
5.1 スレッド同期の基本概念
-
スレッド同期を使用する理由:
同時実行環境では、複数のスレッドが共有リソースに同時にアクセスすると、デッドロックやデータの混乱が発生する可能性がありますが、現時点ではスレッド同期を使用してこれらの問題を解決します。スレッド同期とは、データの異常を回避するために、共有リソースにアクセスするときに複数のスレッドを相互に調整および制御することを指します。
-
スレッド同期の目的:マルチスレッド共有リソースへの秩序ある制御可能なアクセスを実現し、共有リソース データのセキュリティを確保し、デッドロックを回避し、システム全体が正常に実行できるようにすること
-
スレッド同期を実現する方法: ①排他同期(同期ロック実装) ②協調同期
PS : 並列プログラミングでは、共有リソースが増えるほどプログラミングが複雑になるため、共有リソースの使用を可能な限り削減する必要があります。
5.2 スレッドのミューテックス同期
複数のスレッドが並列実行されている場合、各スレッドが占有するCPUリソースや放棄するCPUリソースは微視的に予測できないため、共有データの挿入、削除、更新などの操作に一定の対策を講じないと、取得されるデータが正しくない可能性があります。
- この状況を回避するために、Java ではSynchronized キーワード <synchronizationKeyword> (ロック方式) を使用して相互排他を解決するための同期メカニズムを提供しています。
- 相互排他とは、同時に 1 つのプロセスのみがアクセスできる (クリティカル リソース) 共有リソースを指します。これは一意かつ排他的であり、クリティカル リソースへのスレッド アクセスのアトミック性が保証されます。
- synchronized キーワードの役割: 1 つのスレッドだけが特定のコード ブロックを同時に実行していることを区別すること、つまり、コード ブロックの原子性を実現すること。
synchronized を使用してスレッド同期を実現するには、次の 2 つの方法があります。
- 同期されたコードブロック
- 同期メソッド(インスタンスメソッドとクラスメソッド)
- スレッド同期の実装 - 同期コード ブロック
/**synchronized(obj){
要实现的同步代码
}
**/
-
synchronized で囲まれたコードを同期コードブロックと呼びます
-
同期されたコード ブロックは、コード ブロックのロック オブジェクト (同期ロックとも呼ばれます) と呼ばれるオブジェクトであるパラメーターを受け入れる必要があります。ロック オブジェクトは、コード ブロック内で一意の任意のオブジェクトです。
-
いずれかのスレッドがロック オブジェクトを保持している場合にのみ、ロック オブジェクトによってロックされたコード ブロックを実行できます。
-
スレッドがロック オブジェクトを保持すると、他のスレッドは同時にロック オブジェクトを保持できなくなり、特定のロック オブジェクトに基づいてスレッドの相互排他を実現します。
- PS : ロック オブジェクトを保持しているスレッドは、ロック オブジェクトのメソッドやプロパティにアクセスする他のスレッドには影響しません。
-
複数のスレッドがロック オブジェクトのモニターを競合的に奪い合い、そのスレッドがコード ブロックを実行します。つまり、ロック オブジェクトのモニターが解放されます。
public class Demo01 { private static int num = 0; private Random random = new Random(); //创建一个唯一锁对象 private static Object LOCK = new Object(); /** * 为num+1 * synchronized 同步关键字 * 同步实现两种方式: *1.同步代码块:synchronized (锁对象) {同步代码块} * PS:锁对象可以为任意对象,但是必须唯一,当本类的不同对象在多个线程中被访问不能是this, * 因为this对像不唯一 */ public void add() { /** * 锁对象可以是任意对象但必须唯一,当同步锁对象是当前对象(this对象),并且要同步的代码块是整个 * 方法体时我们就可以使用同步方法实现,即在方法声明位置加入synchronized关键字,同步方法的同步 * 锁是this。 * 但是如果多个线程同时访问的是本类的不同对象时,同步锁则不能使用this,因为this对象不唯一。 **/ synchronized (this) { int result = num + 1; //进入阻塞状态() try { Thread.sleep(random.nextInt(5) + 1); } catch (InterruptedException e) { e.printStackTrace(); } num = result; System.out.println(Thread.currentThread().getName() + ":" + num); } } }
public class Demo01Test { public static void main(String[] args) { Demo01 demo01=new Demo01(); //Demo01 demo02=new Demo01(); new Thread(new Runnable() { @Override public void run() { for (int i=0;i<100;i++){ demo01.add(); } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i=0;i<100;i++){ demo01.add(); } } }).start(); } }
PS: ロック オブジェクトは任意のオブジェクトにすることができますが、一意である必要があります。同期ロック オブジェクトが現在のオブジェクト (このオブジェクト) で、同期されるコード ブロックがメソッド本体全体である場合、同期メソッドを使用してそれを実装できます。つまりメソッド宣言位置キーワードにsynchronizedを追加すると、同期メソッドのロックオブジェクトがこれになります。
ただし、複数のスレッドがこのクラスの異なるオブジェクトに同時にアクセスする場合、このオブジェクトは一意ではないため、同期ロックはこれを使用できません。
-
同期方法
synchronized を使用してメソッド宣言を変更します。このメソッドは同期メソッドと呼ばれます。
- 同期メソッドのロックオブジェクトはこちら
- 同期された静的メソッドのロック オブジェクトは、現在のクラスの .class オブジェクトです。
public synchronized 返回类型 方法名(){ 同步内容} public static synchronized 返回类型 方法名(){ 同步内容}
事例: マルチスレッドを使用して複数のチケット販売所での鉄道チケットの販売をシミュレートする
import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor public class SaleTicket { private Integer ticket; private static final Object LOCK = new Object(); public SaleTicket(Integer ticket) { this.ticket = ticket; } public void sale() { synchronized (LOCK) { if (ticket <= 0) { System.out.println("票已售完...."); return; } System.out.println(Thread.currentThread().getName() + ":售出第" + ticket + "张票"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; } } }
public class SaleTicketTest { public static void main(String[] args) { SaleTicket saleTicket = new SaleTicket(10); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { saleTicket.sale(); } } }, "1号窗口").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { saleTicket.sale(); } } }, "2号窗口").start(); } }
5.3 デッドロック
複数のスレッドが複数の共有リソースを占有して膠着状態になり、外部からの影響がなければこの状態が継続し、この状態がデッドロックになります。
デッドロック条件:①相互排他条件 ②リクエストアンドホールド ③ノンプリエンプティブル ④ループ待機
-
あるスレッドが obj1 のモニタに入って obj2 のモニタを待ち、別のスレッドが obj2 のモニタに入って obj1 のモニタを待ち、この時点でデッドロックになります。
デッドロックは発生しにくく、一度発生するとデバッグが困難です
public class Demo { public static Object LOCK1 = new Object(); public static Object LOCK2 = new Object(); public void f1(){ System.out.println(Thread.currentThread().getName()+"等待获取锁1"); synchronized (LOCK1){ System.out.println(Thread.currentThread().getName()+"获得锁1,等待获取锁2"); synchronized (LOCK2){ System.out.println(Thread.currentThread().getName()+"获得锁2"); } System.out.println(Thread.currentThread().getName()+"释放锁2"); } System.out.println(Thread.currentThread().getName()+"释放锁1"); } public void f2(){ System.out.println(Thread.currentThread().getName()+"等待获取锁2"); synchronized (LOCK2){ System.out.println(Thread.currentThread().getName()+"获得锁2,等待获取锁1"); synchronized (LOCK1){ System.out.println(Thread.currentThread().getName()+"获得锁1"); } System.out.println(Thread.currentThread().getName()+"释放锁1"); } System.out.println(Thread.currentThread().getName()+"释放锁2"); } }
public class Test { public static void main(String[] args) { Demo demo=new Demo(); //线程1 new Thread(new Runnable() { @Override public void run() { demo.f1(); } },"f1").start(); //线程2 new Thread(new Runnable() { @Override public void run() { demo.f2(); } },"f2").start(); } }
5.4 スレッド連携
-
スレッド連携は、マルチスレッド相互排他同期に基づいており、特定の条件に従って意図的かつ計画的にスレッドが相互に対話および連携できるようにする、より高レベルのスレッド同期方式です。
-
Java の適切に設計されたスレッド間通信メカニズムは待機通知メカニズムであり、これによってスレッドの連携が実現されます。
-
待機通知メカニズムは
wait()
、 、notify()
、の 3 つのメソッドによってnotifyAll()
実装されます。- これら 3 つのメソッドは Object で定義され、最終的に変更されたインスタンス メソッドです。
- これら 3 つのメソッドは同期コード内で呼び出す必要があり、ロック オブジェクトのみがこれら 3 つのメソッドを呼び出すことができます。つまり、ロック オブジェクト モニターを保持するスレッドはロック オブジェクトの 3 つのメソッドを呼び出すことができます。
- ロック オブジェクトのスレッドを待機またはウェイクアップします。
-
wait()
方法- このメソッドを呼び出したスレッドは、他のスレッドが同じ同期
notify()
ロックを取得してコールまたはnotifyAll()
メソッドを起動するまで、同期ロックを放棄し、待機状態 (ブロック状態) に入ります。
- このメソッドを呼び出したスレッドは、他のスレッドが同じ同期
-
notify()
方法- 同じ同期ロックを待機しているスレッドに、待機を終了して準備完了状態に入るように通知します。つまり、(現在のスレッドによって保持されている) 同期ロックを待機しているスレッドをウェイクアップします。
-
notifyAll()
方法- 同じ同期ロックを待機しているすべてのスレッドに、待機を終了して準備完了状態に入るように通知します。つまり、(現在のスレッドによって保持されている) 同期ロックを待機しているすべてのスレッドを起動します。
ケース: プロデューサー コンシューマー モデル
/** * 商品类-面包 */ public class MianBao { private Integer num;//面包编号 public MianBao(Integer num) { this.num = num; } }
/** * 面包柜 */ public class GuiZi { private MianBao[] mianBaos;//面包容器 private int size;//柜子中面包数量 /** * 构造方法,初始面包数组 * @param initSize */ public GuiZi(int initSize) { //创建容器并设置大小 mianBaos = new MianBao[initSize]; } /** * 生产面包 * @param mianBao */ public synchronized void add(MianBao mianBao) { /** * 1.生产者线程继续生成,由于面包柜已满,此时size的值为10,面包师傅1线程进入等待状态 * 2.如果此时还未消费,生产者2线程继续生成,由于面包柜已满,此时size的值为10,面包师傅2线程进入等待状态 * 现在处于等待面包师傅线程有2个 * 3.消费者线程消费一个商品,会唤醒所有处于等待的线程,此时生产者1和2两个线程同时被唤醒 * 4.生产者1继续生产,生产后,size的值为10 * 5.生产者1继续生产,由于唤醒后执行的位置是等待的位置,所以不会在检测面包柜是否已满,而此时面包柜满了,所以会报越界异常 */ while (size == mianBaos.length) { System.out.println("柜子已满...."); try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } mianBaos[size] = mianBao;//添加一个面包对象 size++; System.out.println(Thread.currentThread().getName() + ":生产第" + size + "个面包"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } notifyAll(); } /** * 消费面包 * */ public synchronized void sub() { while (size == 0) { System.out.println("柜子已空...."); try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":消费第" + size + "个面包"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } size--; MianBao mianBao = mianBaos[size]; notifyAll(); } }
public class ProAndCon { public static void main(String[] args) { GuiZi guiZi=new GuiZi(10); new Thread(new Runnable() { @Override public void run() { for (int i=0;i< 100;i++){ MianBao mianBao=new MianBao(i+1); guiZi.add(mianBao); } } },"生产者1").start(); new Thread(new Runnable() { @Override public void run() { for (int i=0;i< 100;i++){ MianBao mianBao=new MianBao(i+1); guiZi.add(mianBao); } } },"生产者2").start(); new Thread(new Runnable() { @Override public void run() { for (int i=0;i< 100;i++){ guiZi.sub(); } } },"消费者1").start(); new Thread(new Runnable() { @Override public void run() { for (int i=0;i< 100;i++){ guiZi.sub(); } } },"消费者2").start(); } }
- 学びは西安家中の訓練から生まれる