Java マルチスレッドを理解するための記事 (ネットワーク全体の中で最も詳細)

目次

序文 

1. スレッドの基本的な紹介

1. 中央処理装置 (CPU)

2. 手順

3. プロセス

4. スレッド

5. プロセスとスレッドの関係

6. マルチスレッド開発

6.1 同時実行性 

6.2 並列性 

6.3 シリアル 

7. マルチスレッドの利点 

7.1 マルチスレッドが必要になるのはどんな場合ですか

2. スレッドの作成と起動

1. マルチスレッド実装の原理

2. スレッドの作成と注意事項

3. Threadクラスの共通メソッド 

4. java.lang.Runnable インターフェースを実装する

5. 匿名内部クラスで作成

6. スレッドとランナブルの違い

3. スレッドのライフサイクル

4. スレッドのスケジューリング

1. スレッドの優先順位を調整します。

2. スレッドスリープ

3. 糸の歩留まり

4. sleep() と yield() の違い

5. スレッド結合

5、スレッドの同期

1.コンセプト 

2. 同期排他アクセス(同期)

3. 同期キーワード

4.同期同期コードブロックの実装

5. クラスロックとオブジェクトロックとは何ですか 

5.1 コンセプト

5.2 ロックを解除するタイミング

6. スレッド待機とスレッドウェイクアップ

6. スレッドの安全性の問題 (プロデューサ モデルとコンシューマ モデル)

7、マルチスレッドの小型ケース

1. スロットマシンのケース

2.FX版無制限チャット


序文 

        DOS システムには非常に明白な特徴があります。ウイルスがシステムに侵入すると、システムはそれを即座に記憶します。これは、従来の DOS システムは単一プロセス処理方式を使用しているため、1 つのプログラムのみが単独で実行でき、他のプログラムは実行できません。Windowsではマルチプロセス処理方式が採用されており、同時に複数のプログラムが同時に動作するため、ウイルスに感染してもシステムを正常に利用することができます。スレッドは実際にはプロセスをさらに細分化したもので、プロセスがなければ必ずスレッドは消滅しますし、スレッドが消滅してもプロセスは消滅しない可能性があります。さらに、すべてのスレッドはプロセス ベースで同時に (同時に) 実行されます。

1. スレッドの基本的な紹介

 マルチスレッド: 上司が従業員に自分のために何かをするよう依頼するのと同じです。

1. 中央処理装置 (CPU)

        CPU の中国語名は、論理演算に使用される中央処理装置です。主に、演算装置、コントローラ、レジスタの 3 つの部分で構成されています。文字通り、演算は演算の役割を果たします。コントローラは、CPU の各命令に必要な情報を発行する責任があります。レジスタは、演算や命令を保存するための一時ファイルであり、より高速な処理を保証できます。つまり、スレッドが CPU 上で実行されます。

  • シングルコア: シングルコア CPU は、1 つの時間単位で 1 つのスレッドのみがタスクを実行できるため、誤ったマルチスレッドです。CPUに同時に実行を必要とするマルチスレッドが複数ある場合、CPUは複数のスレッドのうちの1つを交互に実行することしかできませんが、その実行速度は非常に速いため、それを感じられません。
  • マルチコア: マルチコア CPU は、マルチスレッドの効率を向上させることができます。

2. 手順

開発時に書かれたコードをプログラムと呼びます。プログラムはコードの集合、データと命令セットのセットであり、静的な概念です。

3. プロセス

  • CPUはハードディスクからプログラムをメモリに読み込み、そのプログラムのインスタンスをプロセスと呼びます。
  • プログラムが CPU によって複数回メモリに読み込まれると、複数の独立したプロセスになります。プログラムを実行することをプロセスと呼びます。プロセスとは、プログラムを実行する実行プロセスであり、動的な概念である。プロセスにはライフサイクルがあり、プログラムの終了とともにプログラムも破棄されます。プロセスは TCP/IP ポートを介して通信します。

簡単な理解:アプリケーション (プロセスはソフトウェアの一部)、プログラムには少なくとも 1 つのプロセスが含まれ、プロセスには少なくとも 1 つのスレッドが含まれます。

4. スレッド

  • CPU がデータを処理するとき、どの CPU も特定の時点で 1 つのプログラムしか処理できません。
  • スレッドは、プロセス内の実際の動作単位、プロセスのパイプライン、プログラムの実際の実行者、および最小の実行単位です。通常、プロセスには複数のスレッドを含めることができます。スレッドは、CPU のスケジューリングと実行の最小単位です
  • プロセスには複数のスレッドを含めることができます。たとえば、ビデオでは同時に画像を視聴したり、サウンドを聴いたり、集中砲火を視聴したりできます。
  • 多数のスレッドがシミュレートされます。実際のマルチスレッドとは、サーバーなどの複数の CPU、つまりマルチコアが存在することを意味します。シミュレートされたマルチスレッド、つまり 1 つの CPU であれば、同時に実行できるコードは 1 つだけです。切り替えが速いため、同時実行のような錯覚が生じます。

Java プログラムの場合、DOS コマンド ウィンドウに次のように入力します。

  1. 復帰後の Java HelloWorld。JVM が最初に起動され、JVM はプロセスです。
  2. 次に、JVM はメイン メソッドを呼び出すメイン スレッドを開始します (メイン メソッドはメイン スレッドです)。
  3. 同時に、ガベージ コレクション スレッドを開始して、ガベージを処理し、収集します。

注:マルチスレッド メカニズムを使用した後、メイン メソッドの終了はメイン スレッドの終了のみになり、他のスレッドはまだ終了していませんが、メイン スレッドなしでは実行できません。現在の Java プログラムには少なくとも 2 つの同時スレッドがあり、1 つはガベージ コレクション スレッドで、もう 1 つはメイン メソッドを実行するメイン スレッドです。

5. プロセスとスレッドの関係

  • プロセス:現実の企業として見ることができます。
  • スレッド:会社の従業員とみなすことができます。

注: プロセス A とプロセス B のメモリは独立しており、共有されません。マルチスレッド開発

6. マルチスレッド開発

6.1 同時実行性 

        同じオブジェクトが複数のスレッドによって同時に操作されます (これは一種の誤った並列処理です。つまり、1 つの CPU の場合、同じ時点で CPU は 1 つのコードしか実行できません。切り替えが速いため、同時実行のような錯覚が生じます)。

        特徴:複数のタスクを同時に配置し、これらのタスクを相互に散在させることができます。野菜の買い物、メールの送信、足を洗うなど、一部のタスクは並行している場合があります。一部のパスは重複しており、確かに 3 つのことを同時に行っていることになります。ただし、市場に行く、メールを送信する、赤ちゃんを迎えに行くは相互に排他的であり、一度に完了できるのは 1 つだけです。言い換えれば、同時実行により 2 つのタスクが相互に干渉することが可能になります。

6.2 並列性 

あなた(スレッド)はあなたのことを行い、私(スレッド)は私のことを行い、お互いに干渉せず、同時にそれを行います。

6.3 シリアル 

プログラムは現在のプロセスを処理し、次のプロセスを順番に処理します。

特徴: 前のタスクで何もしない場合は、次のタスクを待つだけです。

7. マルチスレッドの利点 

  1. アプリケーションの応答性を向上させます。ヒープ イメージ インターフェイスはより意味があり、ユーザー エクスペリエンスを向上させることができます。
  2. コンピュータ部門のCPU使用率向上
  3. プログラム構造を改善し、長く複雑なプロセスを複数のスレッドに分割し、独立して実行することで、理解と修正が容易になります。

7.1 マルチスレッドが必要になるのはどんな場合ですか

  1. プログラムは 2 つ以上のタスクを同時に実行する必要があります
  2. ユーザー入力、ファイルの読み取りおよび書き込み操作、ネットワーク操作、検索など、待機する必要があるタスクをプログラムで実装する必要がある場合。
  3. バックグラウンドで実行されるいくつかのプログラムが必要な場合。

2. スレッドの作成と起動

1. マルチスレッド実装の原理

        Java 言語の JVM を使用すると、プログラムで複数のスレッドを実行できます。マルチスレッドは Java の java.lang.Thread クラスによって実現できます。

スレッドの特徴:

  • 各スレッドは通常、特定の Thread オブジェクトの run() メソッドを通じて操作を完了します。run() メソッドの本体はスレッド本体と呼ばれることがよくあります。
  • run() を直接呼び出すのではなく、Thread メソッドの start() メソッドを通じてスレッドを開始します。

2. スレッドの作成と注意事項

1 つ目の方法: Thread クラスを継承する

  1. Threadクラスを継承するサブクラスを作成する
  2. Thread クラスの run() メソッドをオーバーライドします。
  3. Threadクラスのサブクラスのオブジェクトを作成する
  4. このオブジェクトに対して start() を呼び出してスレッドを開始します
package com.thread;

/**
 * 我的线程
 * 
 * @author 云村小威
 *
 * @2023年7月21日 下午12:02:44
 */
public class MyThread extends Thread {

	@Override
	public void run() {
		// 编写程序,这段程序运行在分支线程中

	}

}
package com.thread;

/**
 * 
 * @author 云村小威
 *
 * @2023年7月21日 下午12:37:53
 */
public class Text01 {

	public static void main(String[] args) {
		MyThread t = new MyThread();
		// 启动线程
		t.start();
		// run方法不会启动线程,不会分配新的分支栈(这种方式就是单线程)
		t.run();
	}

}

知らせ:

  • t.run() はスレッドを開始しません。これは単なる通常の呼び出しメソッドです。新しいブランチ スタックは割り当てられません。(このメソッドはシングルスタックです)
  • t.start() メソッドの機能は、分岐スレッドを開始し、JVM に新しいスタック領域を開くことであり、このコードのタスクが完了すると、瞬時に終了します。

        このコードのタスクは、新しいスタック スペースを開くことだけです。新しいスタック スペースが開かれている限り、start() メソッドは終了します。スレッドは正常に開始されます。スレッドが正常に開始されると、自動的に run メソッドが呼び出され、run メソッドはブランチ スタックの一番下にあります (スタックをプッシュします)。run メソッドはブランチ スタックの一番下にあり、main メソッドはメイン スタックの一番下にあります。runとmainは等しい。

package com.thread;


/**
 * 我的线程
 * 
 * @author 云村小威
 *
 * @2023年7月21日 下午12:02:44
 */
public class MyThread extends Thread {
	private String name;
	
	public MyThread(String name) {
		this.name=name;
	}
	
	@Override
	public void run() {
		// 编写程序,这段程序运行在分支线程中
		for (int i = 0; i < 5; i++) {
			System.out.println(name+"运行:"+i);
			//休眠
			try {
				sleep((int)Math.random()*10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}
package com.thread;

/**
 * 
 * @author 云村小威
 *
 * @2023年7月21日 下午12:37:53
 */
public class Text01 {

	public static void main(String[] args) {
		MyThread t1 = new MyThread("A");
		MyThread t2 = new MyThread("B");
		// 启动线程
		t1.start();
		t2.start();
	}

}

走る:

知らせ:

  1. start() メソッドが呼び出された後、マルチスレッド コードはすぐには実行されませんが、スレッドは実行可能になります (Runnable)。いつ実行するかはオペレーティング システムによって決定されます。
  2. プログラムの実行結果から、マルチスレッドプログラムが順序どおりに実行されていないことがわかります。したがって、アウトオブオーダー コードのみをマルチスレッド用に設計する必要があります。
  3. Thread.sleep() メソッドを呼び出す目的は、現在のスレッドがプロセスによって取得された CPU リソースを単独で占有するのを防ぎ、他のスレッドが実行するための一定の時間を残すことです。
  4. 実際、すべてのマルチスレッド コードの実行順序は不確実であり、各実行の結果はランダムです。
  5. ただし、start メソッドを繰り返し呼び出すと、java.lang.IllegalThreadStateException が発生します。

3. Threadクラスの共通メソッド 

始める() 現在のスレッドを開始する run() メソッド
走る() 通常、Thread クラスのこのメソッドをオーバーライドし、作成されたスレッドによって実行される操作をこのメソッド内で宣言する必要があります。
現在のスレッド() コード実行の現在のスレッドを返す静的メソッド
getName() 現在のスレッドの名前を取得します
セット名() 現在のスレッドの名前を設定します
収率() 現在実行中のスレッドオブジェクトを一時停止し、他のスレッドを実行する(現在のCPUの実行権を解放する)
加入() 他のスレッドを実行する前に、このスレッドが終了するまで待機する時間 (ミリ秒)
ストップ() 廃止されました。このメソッドが実行されると、現在のスレッドが強制的に終了されます。
スリープ(長いミリ時間) スレッドを指定されたミリ秒間スリープさせます。指定された時間の間、スレッドはブロックされます。
生きている() 現在のスレッドがアクティブかどうかを確認する
setPriority() スレッドの優先順位を変更する
setDaemon() スレッドをデーモン スレッドまたはユーザー スレッドとしてマークします

4. java.lang.Runnable インターフェースを実装する

2 番目の方法: java.lang.Runnable インターフェースを実装する

この手順は Thread クラスの継承と同じです。

package com.thread;


/**
 * 通过实现Runnable接口创建线程
 * 
 * @author 云村小威
 *
 * @2023年7月21日 下午12:02:44
 */
public class MyThread2 implements Runnable {
	private String name;
	
	public MyThread2(String name) {
		this.name=name;
	}
	
	@Override
	public void run() {
		// 编写程序,这段程序运行在分支线程中
		for (int i = 0; i < 5; i++) {
			System.out.println(name+"运行:"+i);
			//休眠
			try {
				Thread.sleep((int)Math.random()*10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}
package com.thread;

/**
 * 
 * @author 云村小威
 *
 * @2023年7月21日 下午12:37:53
 */
public class Text02 {

	public static void main(String[] args) {
		new Thread(new MyThread2("A")).start();
		new Thread(new MyThread2("B")).start();
	}

}

走る:

5. 匿名内部クラスで作成

3 番目の方法: 匿名の内部クラスを使用してスレッドを作成する

package com.thread;

/**
 * 匿名内部类创建线程
 * @author 云村小威
 *
 * @2023年7月21日 下午12:37:53
 */
public class Text03 {
	public static void main(String[] args) {
		Thread t = new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 5; i++) {
					System.out.println("t线程"+i);
				}
			}
		});
		//启动线程
		t.start();
		
		for (int i = 0; i < 5; i++) {
			System.out.println("main线程"+i);
		}
	}

}

走る:

例証します:

  1. Thread2 クラスは、Runnable インターフェイスを通じて、このクラスにマルチスレッド クラスの特性を持たせます。Run() メソッドは、マルチスレッド プログラムの規則です。すべてのマルチスレッド コードは run() メソッド内にあります。Thread クラスは、実際には Runnable インターフェイスを実装するクラスです。
  2. マルチスレッドを開始するときは、まず Thread クラスのコンストラクター Thread (実行可能ターゲット) を通じてオブジェクトを構築し、次に Thread オブジェクトの start() メソッドを呼び出してマルチスレッド コードを実行する必要があります。
  3. 実際、すべてのマルチスレッド コードは、Thread の start () メソッドを実行することによって実行されます。したがって、Thread クラスを拡張する場合でも、Runnable インターフェイスを実装してマルチスレッドを実現する場合でも、スレッドは最終的に Thread オブジェクトの API を通じて制御されます。Thread クラスの API をよく理解することが、マルチスレッド プログラミングの基礎です。

6. スレッドとランナブルの違い

        Thread を継承したクラスはリソース共有には適していません。しかし、Runnable インターフェースが実装されていれば、リソース共有を簡単に実現できます。

概要: Runnable インターフェイスの実装には、Thread クラスの継承よりも利点があります。

  • 同じプログラムコードの複数のスレッドが均一なリソースを処理するのに適しています
  • Javaの単一継承の制限を回避できる
  • プログラムの堅牢性が向上し、コードを複数のスレッドで共有でき、コードとデータが独立します。
  • スレッド プールは、Runnable クラスまたは呼び出し可能なクラスを実装するスレッドにのみ配置でき、Therad を継承するクラスに直接配置することはできません。

        main メソッドもスレッドです。Java ではすべてのスレッドが同時に開始されますが、いつどのスレッドが最初に実行されるかは、誰が最初に CPU リソースを取得するかによって決まります。

        Java では、プログラムが実行されるたびに少なくとも 2 つのスレッドが開始されます。1 つはメイン スレッドで、もう 1 つはガベージ コレクション スレッドです。Java コマンドがクラスを実行するたびに、JVM が実際に開始され、各 JVM インターンシップによってオペレーティング システム内のプロセスが開始されるためです。

3. スレッドのライフサイクル

1. 新しい状態 (new): 新しいスレッド オブジェクトを作成します。

2. 準備完了状態 (実行可能): スレッド オブジェクトが作成された後、他のスレッドがオブジェクトの start() メソッドを呼び出します。この状態のスレッドは実行可能なスレッド プールに配置され、実行可能になり、CPU の使用権を取得するのを待ちます。

3.実行状態 (Running):準備完了状態のスレッドが CPU を獲得し、プログラム コードを実行します。

4. ブロック状態 (Blocked): ブロック状態とは、スレッドが何らかの理由で CPU の使用権を放棄し、一時的に実行を停止することを意味します。スレッドが準備完了状態になるまで、実行状態に移行することはできません。

ブロックには 3 つのタイプがあります。

  • ブロックを待機中:実行中のスレッドは wait() メソッドを実行し、JVM はスレッドを待機プールに入れます。(wait メソッドは保持されているロックを解放します)
  • 同期ブロック:実行中のスレッドがオブジェクトの同期ロックを取得するときに、同期ロックが他のスレッドによって占有されている場合、JVM はスレッドをロック プールに入れます。
  • その他のブロック:実行中のスレッドが sleep () または join () メソッドを実行するか、I/O リクエストを発行すると、JVM はスレッドをブロック状態にします。sleep () 状態がタイムアウトになると、join () はスレッドが終了するかタイムアウトになるまで待機します。あるいは、I/O 処理が完了すると、スレッドは再び準備完了状態に移行します。(スリープしても保持されたロックは解除されません)

デッド状態 (Dead): スレッドは実行を終了するか、例外により run() メソッドを終了し、スレッドのライフサイクルが終了します。

4. スレッドのスケジューリング

1. スレッドの優先順位を調整します。

Java スレッドには優先順位があり、優先順位が高いスレッドほど実行される機会が多くなります。

Java スレッドの優先順位は、1 ~ 10 の範囲の整数で表されます。Thread クラスには、次の 3 つの静的定数があります。

static int MAX_PRIORITY
        线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
        线程可以具有最低优先级,取值为1。
static int NORM_PRIORITY
        分配给线程的默认优先级,取值为5。

Thread クラスの setPriority メソッドと getPriority() メソッドは、それぞれスレッドの優先順位を設定および取得するために使用されます。

各スレッドにはデフォルトの優先順位があり、メインスレッドのデフォルトの優先順位は Thread.NORM_PRIORITY です。

使用法:

Thread t1 = new Thread("t1");
Thread t2 = new Thread("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORIRY);

知らせ: 

        優先順位を設定したら必ず先に実行するというわけではなく、コンピュータの性能などの影響により、多少のランダム性はありますが、優先される確率は高くなります。

2. スレッドスリープ

Thread.sleep (long millis (ミリ秒)):スレッドをスリープ (ブロック状態) にして、CPU タイム スライスを放棄し、他のスレッドで使用できるようにします。

sleep メソッドを使用すると、特定のコード部分を特定の時間間隔および実行頻度で実行できます。

package com.thread;

/**
 * 线程睡眠
 * @author 云村小威
 *
 * @2023年7月21日 下午12:37:53
 */
public class Text04 {
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+"-->"+i);
			//睡眠0.8秒
			try {
				Thread.sleep(800);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
	}
}

走る:

3. 糸の歩留まり

Thread.yield() メソッド:現在実行中のスレッド オブジェクトを一時停止し、同じまたはより高い優先度のスレッドに実行の機会を与えます。

package com.thread;

/**
 * 线程让步
 * @author 云村小威
 *
 * @2023年7月21日 下午12:37:53
 */
public class Text05 {
	public static void main(String[] args) {
		Thread t = new Thread(new Runnable() {
		
			@Override
			public void run() {
				for (int i = 0; i < 15; i++) {
					Thread.yield();
					System.out.println(Thread.currentThread().getName()+"-->"+i);
				}
				
			}
		});
		t.setName("t");
		t.start();
		
		//主线程
		for (int i = 0; i < 15; i++) {
			System.out.println(Thread.currentThread().getName()+"-->"+i);
		}
	}
}

実行結果: 実行する前にメインスレッドの実行を終了させて​​ください。 

4. sleep() と yield() の違い

  1. sleep() は現在のスレッドを停滞状態にするため、sleep() を実行しているスレッドは指定された時間内に実行されません。yield() は現在のスレッドを実行可能状態に戻すだけなので、yield() を実行しているスレッドは実行可能状態に入った直後に実行される可能性があります。
  2. sleep メソッドは、現在実行中のスレッドを一定期間スリープさせて非実行状態にします。この期間の長さはプログラムによって設定されます。yield メソッドは、現在のスレッドに CPU の占有を放棄させますが、放棄する時間は設定できません。
  3. 実際、yield() メソッドは次の操作に対応します。まず、同じ実行可能な状態で同じ優先度を持つスレッドが現在存在するかどうかを確認し、存在する場合は、CPU 所有権をこのスレッドに与えます。そうでない場合は、元のスレッドの実行を継続します。したがって、yield() メソッドは「give in」と呼ばれ、同じ優先順位の他のスレッドに実行の機会を譲ります。
  4. また、sleep メソッドは優先度の低いスレッドに実行のチャンスを与えますが、yield() メソッドが実行されるとき、現在のスレッドはまだ実行可能な状態にあるため、しばらくの間、優先度の低いスレッドに CPU 所有権を取得させることはできません。実行中のシステムでは、優先順位の高いスレッドがスリープ メソッドを呼び出しておらず、I\O によってブロックされていない場合、優先順位の低いスレッドは、優先順位の高いすべてのスレッドの実行が終了するまで待機することしかできず、実行する機会が得られません。

5. スレッド結合

Thread.join() メソッド:他のスレッドが終了するのを待つために使用されます。
        スレッド B の join メソッドがスレッド A で呼び出された場合、スレッド A はスレッド B の実行が完了するまでブロックされ、スレッド A はブロック状態から準備完了状態に変わり、CPU の使用権の取得を待ちます。join メソッドは start メソッドが呼び出された後でのみ有効であり、スレッドは参加する前に開始されている必要があります。

package com.thread;

/**
 * 线程加入
 * 
 * @author 云村小威
 *
 * @2023年7月21日 下午12:37:53
 */
public class Text06 {
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new JoinThread("t1"));
		Thread t2 = new Thread(new JoinThread("t2"));
		t1.start();
		t1.join();
		t2.start();
	}
}

class JoinThread implements Runnable {
	private String name;

	public JoinThread(String name) {
		this.name = name;
	}

	@Override
	public void run() {
		for (int i = 1; i <= 3; i++) {
			System.out.println(name + "-->" + i);
		}
	}
}

操作結果:

 メインスレッドは、子スレッドがすべて終了するまで確実に待ってから終了します。

5、スレッドの同期

1.コンセプト 

        つまり、スレッドがメモリ上で動作しているとき、他のスレッドはこのメモリ アドレス上で動作できません。スレッドが動作を完了するまで、他のスレッドは待機状態にありながら、そのアドレス上で動作できます。スレッドの同期を実現するには、さまざまな方法があります。

1. マルチスレッドを作成する理由は何ですか?

一般に、スレッドを作成してもプログラムの実行効率は向上しないため、複数のスレッドを作成する必要があります。

2. なぜスレッド同期なのか?

複数のスレッドが同時に実行されている場合、スレッド関数が呼び出されることがありますが、複数のスレッドが同じメモリ アドレスに同時に書き込むと、CPU 時間のスケジューリングの問題により、書き込まれたデータが複数回上書きされるため、スレッドを同期する必要があります。

3. スレッド同期とは何を意味しますか?

同期とは、あらかじめ決められた順序で実行される調整されたペースです。例: あなたは終わりました、私はもう一度やります。

誤解「同じ」という言葉は一緒に行動していると理解されやすいですが、そうではありません。「同じ」という言葉は、調整、援助、相互協力を意味する必要があります。

正しい理解:いわゆる同期とは、関数呼び出しが発行されたときに、結果が得られるまで呼び出しは返されず、他のスレッドがこのメソッドを同時に呼び出すことができないことを意味します。

スレッド同期の役割:

  • スレッドは、メモリ、ファイル、データベースなどの一部のリソースを他のスレッドと共有する場合があります。
  • 複数のスレッドが同じ共有リソースを同時に読み書きすると、競合が発生する可能性があります。現時点では、スレッドの「同期」メカニズムを導入する必要があります。つまり、各スレッド間には先着順のスレッドが存在する必要があり、群がってそれらを一緒に取得することはできません。
  • スレッド同期の本当の意味は、実際には「キューイング」です。複数のスレッドがキューに登録され、共有リソースを同時にではなく 1 つずつ操作する必要があります。

2. 同期排他アクセス(同期)

        基本的に、スレッドの安全性の問題に対するすべての解決策は、「シリアル化されたクリティカル リソース アクセス」の方法を使用します。つまり、1 つのスレッドだけがクリティカル リソースを同時に操作し、操作が完了した後に他のスレッドが操作できるようになります。これは、同期相互排他アクセスとも呼ばれます。

Javaでは、同期排他アクセスを実現するために、synchronizedとLockが一般的に使用されます。

3. 同期キーワード

まずミューテックスについて理解します。ミューテックスは相互排他アクセスの目的を達成できるロックです。

  1. ミューテックスが変数に追加される場合、その変数に同時にアクセスできるのは 1 つのスレッドだけです。つまり、スレッドが重要なリソースにアクセスするとき、他のスレッドは待機することしかできません。
  2. Javaでは各オブジェクトにロックマーク(モニター)があり、複数のスレッドがオブジェクトにアクセスする場合、そのオブジェクトを取得したロックのみにアクセスできます。
  3. コードを記述するとき、同期を使用してオブジェクトのメソッドまたはコード ブロックを変更できます。スレッドがオブジェクトの同期メソッドまたはコード ブロックにアクセスすると、オブジェクトのロックを取得します。

4.同期同期コードブロックの実装

  1. 1つ目の方式:同期コードブロック方式(フレキシブル)
  2. 2 つ目: インスタンス メソッドで synchronized を使用する
  3. 3 番目: 静的メソッドで同期を使用する
package com.thread;

/**
 * synchronized同步代码块实现
 * 
 * @author 云村小威
 *
 * @2023年7月21日 下午12:37:53
 */
public class Text07 {
	// 第一种
	public void myThread1(Thread thread) {
		// this代表当前对象
		synchronized (this) {
			for (int i = 0; i < 5; i++) {
				System.out.println(thread.getName() + ":" + i);
			}
		}
	}

	// 第二种
	public synchronized void myThread2(Thread thread) {
		// this代表当前对象
		for (int i = 0; i < 5; i++) {
			System.out.println(thread.getName() + ":" + i);
		}
	}

	// 第三种
	public static synchronized void myThread3(Thread thread) {
		// this代表当前对象
		for (int i = 0; i < 5; i++) {
			System.out.println(thread.getName() + ":" + i);
		}
	}

}

5. クラスロックとオブジェクトロックとは何ですか 

5.1 コンセプト

オブジェクト ロック: Java では、各オブジェクトに一意のロックがあります。オブジェクト ロックは、オブジェクト インスタンス メソッドまたはオブジェクト インスタンスに対して使用されます (1 つのオブジェクトに対して 1 つのロック、100 個のオブジェクトに対して 100 のロック)。

クラス ロック:クラスの静的メソッドまたはクラス オブジェクトに使用されます。クラスのインスタンス オブジェクトは複数存在できますが、クラス オブジェクトは 1 つだけです (オブジェクト 100 個、ロックは 1 つだけです)。

上記の 3 番目の方法は、同期された同期コード ブロックを実現することであることに注意してくださいクラスに属する静的メソッドに同期ロックを追加し、このクラスのすべてのオブジェクトがこのロックを共有します。これはクラス ロックと呼ばれます。

5.2 ロックを解除するタイミング

        メソッドまたはコード ブロックが synchronized キーワードによって変更された場合、スレッドがメソッドまたはコード ブロックのロックを取得すると、他のスレッドはメソッドまたはコード ブロックにアクセスし続けることができなくなります。他のスレッドがメソッドまたはコード ブロックにアクセスしたい場合は、ロックを取得したスレッドがロックを解放するまで待つ必要があります。ここでロックを解放するケースは 2 つだけです。

  1. スレッドがコード ブロックを実行すると、ロックは自動的に解放されます。
  2. プログラムはエラーを報告し、JVM はスレッドに自動的にロックを解放させます。

例: ここでは、同期を達成するための上記の 1 番目と 2 番目の方法を呼び出します。

public static void main(String[] args) {
		new Text07().myThread1(new Thread());
		new Text07().myThread2(new Thread());
	}

6. スレッド待機とスレッドウェイクアップ

オブジェクトクラス 効果
無効待機() アクティビティを現​​在のオブジェクトのスレッドで無期限に待機させます (以前に保持されていたロックを解放します)
無効通知() 現在のオブジェクトが待機しているスレッドをウェイクアップします (ウェイクアップを求めるプロンプトのみが表示され、ロックは解放されません)
すべて通知を無効にする() このオブジェクトのモニターで待機しているすべてのスレッドを起動します。

詳細な方法:

        wait メソッドとnotify メソッドはスレッド オブジェクトのメソッドではありませんが、Java の任意の Java オブジェクトのメソッドです。これら 2 つのメソッドはオブジェクト クラスに付属しているためです。wait メソッドとnotify メソッドは、スレッド オブジェクトを通じて呼び出されません。

6. スレッドの安全性の問題 (プロデューサ モデルとコンシューマ モデル)

1.「生産者と消費者のパターン」とは何ですか?

  1. 生産ラインは生産を担当し、コンシューマ スレッドは消費を担当します。
  2. 生産スレッドと消費スレッドのバランスが取れている必要があります
  3. これは特別なビジネス要件であり、この特別な場合には wait メソッドと notification メソッドを使用する必要があります。

2. ビジネス要件をシミュレーションする

  • ウェアハウスには List コレクションを使用します
  • List コレクションには 1 つの要素のみを格納できると想定されています。
  • 1 要素は倉庫がいっぱいであることを意味します
  • List コレクション内の要素の数が 0 の場合、ウェアハウスが空であることを意味します
  • List コレクションには常に最大 1 つの要素が格納されることを保証します。
  • この効果は達成されなければなりません: 1 を生成し、1 を消費します。

生産者と消費者のケースをシミュレートします。 

        まず、生産する必要があるオブジェクトを定義する製品クラスを作成し、次にファクトリー クラスを作成し、生産と販売の 2 つのメソッドを記述し、同期ロックを実装し、生産された鶏の足を put メソッドでコンテナに保存しました。コンテナが 10 未満の場合はコンシューマーは待機し (wait() メソッド)、それより大きい場合は待機しているコンシューマーをすべてウェイクアップします (notifyAll() メソッド)。次に、処理を続ける必要があるプロデューサ クラスを定義します。いつ休むかは、ファクトリ クラスによって生成された鶏の足が 20 より大きいかどうかによって決まります。それ以外の場合は、休んで処理を続けることができません。最後に、消費者は工場クラスの販売メソッドを呼び出し、鶏の足を保管するためのコンテナが 10 個を超える限り、コンテナを購入して減らし始めます。それ以上ない場合は、消費者は待つように求められます。

package com.thread;

import java.util.Vector;

/**
 * 生产者与消费者案例
 * 
 * @author 云村小威
 *
 * @2023年7月22日 上午10:51:47
 */
public class Text08 {

	public static void main(String[] args) {
		// 店铺运营
		// 创建一个工厂
		Factory f = new Factory();

		// 招三个成产员工
		Producer p1 = new Producer(f);
		Producer p2 = new Producer(f);
		Producer p3 = new Producer(f);

		new Thread(p1, "工作人员A").start();
		new Thread(p2, "工作人员B").start();
		new Thread(p3, "工作人员C").start();

		// 引入三个消费者
		Sale s1 = new Sale(f);
		Sale s2 = new Sale(f);
		Sale s3 = new Sale(f);

		new Thread(s1, "消费者A").start();
		new Thread(s2, "消费者B").start();
		new Thread(s3, "消费者C").start();
	}

}

/**
 * 消费者
 */
class Sale implements Runnable {
	Factory f = null;

	public Sale(Factory f) {
		this.f = f;
	}

	public void run() {
		while (true) {
			// 让消费者延迟购买的时间
			try {
				Thread.sleep(1500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			f.sale();
		}
	};

}

/**
 * 生产类
 */
class Producer implements Runnable {
	Factory f = null;

	public Producer(Factory f) {
		this.f = f;
	}

	@Override
	public void run() {
		// 需要不停的做事
		while (true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// 调用生产数据的方法
			f.put(new Product(1, "鸡脚"));
		}
	}

}

/**
 * 工厂类
 */
class Factory {
	// 准备储存产品的集合容器,多线程优先使用Vector集合,该集合类中的方法大部分都是带有同步的概念机制。
	Vector<Product> vc = new Vector<Product>();

	// 专门定义一个生产数据方法
	public synchronized void put(Product product) {
		// 判断产品于目标数量是否相同
		if (vc.size() >= 20) {
			// 产品数量满足让工作人员处于等待状态
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			// 让等待的工作人员生产产品
			this.notifyAll();
			System.out.println("正在生产中...当前货架的鸡脚数目是:" + vc.size());
			vc.add(product);
			System.out.println("货架鸡脚总数目是:" + vc.size());
		}
	}

	// 专门定义一个负责消费数据方法
	public synchronized void sale() {
		// 只要容器存在货物就可以进行购买
		if (vc.size() > 10) {
			this.notifyAll(); // 唤醒所有等待的消费者
			System.out.println("当前货架上的鸡脚数目是:" + vc.size() + ",正在出售鸡脚。");
			vc.remove(0);
			System.out.println("\t目前货架上的鸡脚数目是:" + vc.size());
		} else {
			// 没有鸡脚,让消费者处于等待状态
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

/**
 * 产品类
 */
class Product {
	private int pid;
	private String name;

	public Product() {
		// TODO Auto-generated constructor stub
	}

	public int getPid() {
		return pid;
	}

	public void setPid(int pid) {
		this.pid = pid;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Product(int pid, String name) {
		super();
		this.pid = pid;
		this.name = name;
	}

}

7、マルチスレッドの小型ケース

1. スロットマシンのケース

package com.thread;

import java.util.Random;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

/**
 * 老虎机游戏程序
 * @author 云村小威
 *
 * @2023年7月22日 上午11:27:56
 */
public class Game extends Application {

	Random rd = new Random(); // 随机数对象
	boolean flag = true; // 控制开关默认打开
	int timer = 50; // 控制事件变量
	// 通过数组保存没有规律的多张图片
	String[] images = { "lib\\7.png", "lib\\橙子.png", "lib\\柠檬.png", "lib\\苹果.png", "lib\\西瓜.png", "lib\\香蕉.png", };

	// 总布局
	Pane pane = new Pane();
	private Label a1 = new Label("", new ImageView(new Image("lib\\苹果.png", 150, 150, false, false)));
	private Label a2 = new Label("", new ImageView(new Image("lib\\西瓜.png", 150, 150, false, false)));
	private Label a3 = new Label("", new ImageView(new Image("lib\\柠檬.png", 150, 150, false, false)));

	private Button btn1 = new Button("开始游戏");
	private Button btn2 = new Button("点击停止");

	{
		a1.setStyle("-fx-border-color:#ccc;");
		a1.setLayoutX(60);
		a1.setLayoutY(40);
		pane.getChildren().add(a1);

		a2.setStyle("-fx-border-color:#ccc;");
		a2.setLayoutX(225);
		a2.setLayoutY(40);
		pane.getChildren().add(a2);

		a3.setStyle("-fx-border-color:#ccc;");
		a3.setLayoutX(390);
		a3.setLayoutY(40);
		pane.getChildren().add(a3);

		btn1.setLayoutX(180);
		btn1.setLayoutY(220);
		btn1.setPrefSize(100, 40);
		pane.getChildren().add(btn1);

		btn2.setLayoutX(340);
		btn2.setLayoutY(220);
		btn2.setPrefSize(100, 40);
		pane.getChildren().add(btn2);
	}

	@Override
	public void start(Stage stage) throws Exception {
		// 创建一个场景
		Scene scene = new Scene(pane, 600, 300);
		stage.setScene(scene);
		stage.show();

		/**
		 * 开始游戏点击事件
		 */
		btn1.setOnAction(e -> {
			flag = true;
			new Thread() {
				public void run() {
					while (flag) {
						try {
							Thread.sleep(timer);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}

						Platform.runLater(new Runnable() {
							@Override
							public void run() {
								int sj1 = rd.nextInt(6);
								a1.setGraphic(new ImageView(new Image(images[sj1])));
								int sj2 = rd.nextInt(6);
								a2.setGraphic(new ImageView(new Image(images[sj2])));
								int sj3 = rd.nextInt(6);
								a3.setGraphic(new ImageView(new Image(images[sj3])));
							}
						});

						timer += 6;
						if (timer >= 300) {
							timer = 50;
							break;
						}
						
					}
				}
			}.start();

		});

		/**
		 * 停止点击事件
		 */
		btn2.setOnAction(e -> {
			flag = false;
		});

	}

	public static void main(String[] args) {
		launch();
	}

}

実行結果:

2.FX版無制限チャット

 1.サーバー

package com.net;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 * 服务器
 * 
 * @author 云村小威
 *
 * @2023年7月19日 下午9:40:54
 */
public class ServerFx extends Application {
	/**
	 * 总布局 BorderPane
	 */
	BorderPane bor = new BorderPane();

	// 上
	HBox hb = new HBox();
	TextField t = new TextField();
	Button btn = new Button("启动服务");
	{
		hb.getChildren().addAll(t, btn);
		hb.setAlignment(Pos.CENTER);
		hb.setPadding(new Insets(20));
		hb.setSpacing(20);
		btn.setPrefSize(100, 40);
		bor.setTop(hb);
	}

	// 中
	TextArea ta = new TextArea();
	{
		ta.setEditable(false); // 文本域不可修改
		ta.setStyle("-fx-border-color: #ccc;-fx-border-width: 10 10 10 10;");
		bor.setCenter(ta);
	}

	// 下
	HBox hb2 = new HBox();
	TextField text = new TextField();
	Button btn1 = new Button("发送");
	{
		hb2.getChildren().addAll(text, btn1);
		hb2.setAlignment(Pos.CENTER);
		hb2.setPadding(new Insets(20));
		hb2.setSpacing(20);
		bor.setBottom(hb2);
	}

	/**
	 * 将通信对象声明在start方法外面
	 */
	Socket s = null;
	ServerSocket ss = null;

	/**
	 * 定义所需的流对象 扩大权限方便关闭流
	 */
	InputStream inputStream = null;
	BufferedReader br = null;
	OutputStream outputStream = null;
	BufferedWriter bw = null;

	@Override
	public void start(Stage stage) throws Exception {
		stage.setTitle("服务器");
		Scene scene = new Scene(bor, 800, 700);
		stage.setScene(scene);
		stage.show();

		/**
		 * 启动服务点击事件
		 */
		btn.setOnAction(e -> {
			// 创建服务器
			try {
				ss = new ServerSocket(Integer.parseInt(t.getText()));
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			ta.appendText("服务已启动,等待客户端连接...\n");
			// 开启一个线程 自动接受客户端发送的信息
			new Thread() {
				public void run() {
					// 多线程控制接受客户端信息
					try {
						s = ss.accept();
						ta.appendText("客户端已连接...\n");
					} catch (IOException e) {
						e.printStackTrace();
					}
					while (true) {
						// 读取 获取输入流
						try {
							inputStream = s.getInputStream();
							br = new BufferedReader(new InputStreamReader(inputStream));
							String count = br.readLine();
							ta.appendText("\t客户端:" + count + "\n");
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
			}.start();
		});

		/**
		 * 发送信息点击事件
		 */
		btn1.setOnAction(e -> {
			try {
				outputStream = s.getOutputStream();
				bw = new BufferedWriter(new OutputStreamWriter(outputStream));
				// 获取文本框内容
				String count = text.getText();
				bw.write(count);
				bw.newLine();
				bw.flush();
				ta.appendText("服务器:" + count + "\n");
				text.setText("");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		});
	}

	public static void main(String[] args) {
		launch();
	}
}

2. クライアント

package com.net;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;

/**
 * 客户端
 * 
 * @author 云村小威
 *
 * @2023年7月19日 下午9:41:46
 */
public class ClientFx extends Application {
	/**
	 * 总布局 BorderPane
	 */
	BorderPane bor = new BorderPane();

	// 上
	HBox hb = new HBox();
	Text ip = new Text("IP");
	TextField t = new TextField("127.0.0.1");
	Text tcp = new Text("端口号");
	TextField t2 = new TextField();
	Button btn = new Button("连接");
	{
		t.setDisable(true);// 设置ip地址不可编辑
		hb.getChildren().addAll(ip, t, tcp, t2, btn);
		hb.setAlignment(Pos.CENTER);
		hb.setPadding(new Insets(20));
		hb.setSpacing(20);
		btn.setPrefSize(100, 40);
		bor.setTop(hb);
	}

	// 中
	TextArea ta = new TextArea();
	{
		ta.setEditable(false); // 文本域不可修改
		ta.setStyle("-fx-border-color: #ccc;-fx-border-width: 10 10 10 10;");
		bor.setCenter(ta);
	}

	// 下
	HBox hb2 = new HBox();
	TextField text = new TextField();
	Button btn1 = new Button("发送");
	{
		hb2.getChildren().addAll(text, btn1);
		hb2.setAlignment(Pos.CENTER);
		hb2.setPadding(new Insets(20));
		hb2.setSpacing(20);
		bor.setBottom(hb2);
	}

	/**
	 * 定义所需的流对象 扩大权限方便关闭流
	 */
	InputStream inputStream = null;
	BufferedReader br = null;
	OutputStream outputStream = null;
	BufferedWriter bw = null;

	// 定义客户端对象
	Socket s = new Socket();

	@Override
	public void start(Stage stage) throws Exception {

		stage.setTitle("客户端");
		stage.setResizable(false); // 设置窗口不可动
		Scene scene = new Scene(bor, 800, 700);
		stage.setScene(scene);
		stage.show();

		/**
		 * 连接点击事件
		 */
		btn.setOnAction(e -> {
			String ip = t.getText(); // 获取ip地址
			int port = Integer.parseInt(t2.getText());
			try {
				s = new Socket(ip, port);
				ta.appendText("客户端成功连接服务器...\n");
			} catch (IOException e1) {
				e1.printStackTrace();
			}

			// 开启一个线程 (让它自动接受数据)
			new Thread() {
				public void run() {
					while (true) {
						try {
							inputStream = s.getInputStream();
							br = new BufferedReader(new InputStreamReader(inputStream));
							String count = br.readLine();
							ta.appendText("\t服务器:" + count + "\n");
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
			}.start();
		});

		/**
		 * 发送信息点击事件
		 */
		btn1.setOnAction(e -> {
			try {
				outputStream = s.getOutputStream();
				bw = new BufferedWriter(new OutputStreamWriter(outputStream));
				// 获取文本框内容
				String count = text.getText();
				bw.write(count);
				bw.newLine();
				bw.flush();
				ta.appendText("客户端:" + count + "\n");
				text.setText("");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		});

	}

	public static void main(String[] args) {
		launch();
	}

}

 実行結果:


おすすめ

転載: blog.csdn.net/Justw320/article/details/131848634