Java学習記録(中級)-[5]、マルチスレッド

(1)複数のスレッドを作成するには、次の3つの方法があります。↓↓↓ 

             ①スレッドクラスを継承する

             ②Runnableインターフェースを実装する

             ③匿名クラス

// 继承线程类的方式
public class MultiThreadTest {

	public static void main(String[] args) {
		
        // 创建线程1
		MyThread thread1 = new MyThread();
        // 创建线程2
		MyThread thread2 = new MyThread();

		thread1.start();    // 启动线程1
		thread2.start();    // 启动线程2

	}

}

class MyThread extends Thread{
	
	@Override
	public void run() {
		for (int i = 0; i < 30; i++) {
			System.out.println("我是线程【" + this.getName() + "】");
		}
	}
}

実行結果:

// 实现Runnable接口的方式
public class MultiThreadTest {

	public static void main(String[] args) {

		MyRunnable runnable1 = new MyRunnable(1);
		MyRunnable runnable2 = new MyRunnable(2);
		new Thread(runnable1).start();
		new Thread(runnable2).start();

	}

}

class MyRunnable implements Runnable {

	int a = 0;

	public MyRunnable(int a) {
		this.a = a;
	}

	@Override
	public void run() {
		for (int i = 0; i < 30; i++) {
			System.out.println("我是线程【" + a + "】");
		}
	}

}

実行結果:

// 匿名类的方式
public class MultiThreadTest {

	public static void main(String[] args) {
		
		new Thread() {
			public void run() {
				for (int i = 0; i < 30; i++) {
					System.out.println("我是线程【" + this.getName() + "】");
				}
			};
		}.start();
		
		new Thread() {
			public void run() {
				for (int i = 0; i < 30; i++) {
					System.out.println("我是线程【" + this.getName() + "】");
				}
			};
		}.start();

	}

}

実行結果:

(2)一般的なスレッドメソッド

メソッド名 特徴
睡眠 現在のスレッドは一時停止しています
参加する 現在のスレッドに参加します(現在のスレッドはスレッドの終了が実行を継続するのを待ちます)
setPriority スレッドの優先度を設定する
産出 一時停止
setDaemon デーモンスレッド

[優先度の高いスレッドは、CPUリソースを取得する可能性が高くなります]

[現在のスレッドは一時的に中断されているため、他のスレッドがCPUリソースを占有する機会が増える可能性があります]

[デーモンスレッドの概念は次のとおりです。プロセス内のすべてのスレッドがデーモンスレッドの場合、現在のプロセスを終了します]

[デーモンスレッドは通常、ロギング、パフォーマンス統計などに使用されます]

(3)マルチスレッドの同期の問題

マルチスレッド同期の問題とは、複数のスレッドが1つのデータを同時に変更した場合に発生する可能性のある問題を指します 

例えば:

自宅のある銀行カードには10万元あります。この日、妻は買い物に出かけましたが、同時に夫の給料がこのカードに適用されます。

夫の給料が入ってきたとき、銀行カードの残高は10万、給与は2万だったので、銀行カードの最終的な残高は12万になるはずです。

しかし同時に、妻は服を買います。現時点では、夫の給料はまだ支払われておらず、残高は10万元です。支払額は1,000元で、残りは99,000元になるはずです。

その結果、悲劇が起こり、少し前に夫の給料が来て、残高が12万になるとすぐに99,000になりました。

2万元安い!

[1]、問題解決のアイデア

デポジットスレッドが残高にアクセスしている間、他のスレッドは残高にアクセスできません

[2]、同期同期オブジェクトの概念

Object someObject =new Object();
synchronized (someObject){
  //此处的代码只有占有了someObject后才可以执行
}

同期とは、現在のスレッド、排他オブジェクトsomeObject、
現在のスレッドがオブジェクトsomeObjectを独占することを意味します。別のスレッドがオブジェクトsomeObjectを占有しようとすると、現在のスレッドがsomeObjectの占有を解放するまで待機します。
someObjectは同期オブジェクトとも呼ばれます。すべてのオブジェクトを同期オブジェクトとして使用できます。
同期効果を実現するには、同じ同期オブジェクトを使用する必要があります

。同期オブジェクトを解放する方法:同期ブロックが自然に終了するか、例外がスローされます。

[3]、スレッドセーフクラス

メソッドがすべて同期されて変更されるクラスの場合、そのクラスはスレッドセーフクラスと呼ばれます。

同時に、このクラスのインスタンスに入力してデータを変更できるのは1つのスレッドのみであるため、このインスタンスのデータの安全性が確保されます。 (複数のスレッドによって同時に変更されてダーティデータになることはありません)

(4)、スレッドセーフクラス

HashMap 和 HashTable 的区别:

区别1: 
        HashMap    可以存放 null
        HashTable  不能存放 null
区别2:
        HashMap    不是线程安全的类
        HashTable  是线程安全的类
StringBuffer 和 StringBuilder 的区别:

StringBuffer  是线程安全的
StringBuilder 是非线程安全的

所以当进行大量字符串拼接操作的时候:
    如果是单线程,就用StringBuilder会更快些,
    如果是多线程,就需要用StringBuffer 保证数据的安全性

【非线程安全的为什么会比线程安全的 快? 因为不需要同步嘛,省略了些时间】
ArrayList 和 Vector 的区别:

ArrayList  不是线程安全的类
Vector     是线程安全的类

【其它一模一样】
把非线程安全的集合转换为线程安全:

借助 Collections.synchronizedList() , 可以把非线程安全的集合转换为线程安全的集合

(5)、デッドロック

[1]、デモのデッドロック

1. 线程1 首先占有对象1,接着试图占有对象2
2. 线程2 首先占有对象2,接着试图占有对象1
3. 线程1 等待线程2释放对象2
4. 与此同时,线程2等待线程1释放对象1
就会。。。一直等待下去,直到天荒地老,海枯石烂,山无棱 ,天地合。。。

public class DeadLockTest {

	public static void main(String[] args) {
		final Hero ahri = new Hero();
		ahri.name = "九尾妖狐";
		final Hero annie = new Hero();
		annie.name = "安妮";

		Thread t1 = new Thread() {
			public void run() {
				// 占有九尾妖狐
				synchronized (ahri) {
					System.out.println("t1 已占有九尾妖狐");
					try {
						// 停顿1000毫秒,另一个线程有足够的时间占有安妮
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

					System.out.println("t1 试图占有安妮");
					System.out.println("t1 等待中 。。。。");
					synchronized (annie) {
						System.out.println("do something");
					}
				}

			}
		};
		t1.start();
		Thread t2 = new Thread() {
			public void run() {
				// 占有安妮
				synchronized (annie) {
					System.out.println("t2 已占有安妮");
					try {

						// 停顿1000秒,另一个线程有足够的时间占有暂用九尾妖狐
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println("t2 试图占有九尾妖狐");
					System.out.println("t2 等待中 。。。。");
					synchronized (ahri) {
						System.out.println("do something");
					}
				}

			}
		};
		t2.start();
	}

}

class Hero {
	String name;
}

結果:

(6)スレッド間の相互作用

スレッド間でインタラクティブな通知が必要です。次の状況を考慮してください 
。同じヒーローを処理するスレッドが2つあります。 
1つは血液を増やし、もう1つは血液を減らします。 

減血スレッドは、血液量= 1であることを検出し、増血スレッドがヒーローに血液を追加するまで減血を停止します。その後、減血を続けることができます。

[1]、愚かな方法

whileループは、減血スレッドで1かどうかを判断するために使用されます。1の場合、増血スレッドが血液量を回復するまでループは停止しません。
これは、CPUを大量に消費し、パフォーマンスを低下させるため、悪い解決策です。

[2]、待機を使用してスレッドの相互作用を通知します

Heroクラスの場合:hurt()失血メソッド:hp = 1の場合、this.wait()を実行します
。this.wait()は、これを所有するスレッドが待機
し、hurtメソッドに入ったスレッドを一時的に解放することを意味します。減血スレッドthis.wait()は、減血スレッドに一時的にこれの所有権を解放させます。このように、血液追加スレッドには、recover()血液追加メソッドに入る機会があります。


血液を追加するRecover()メソッド:血液量を増やし、this.notify();を実行します
。this.notify()は、これで待機しているスレッドにウェイクアップできることを通知することを意味します。これを待っている糸はまさに血を減らす糸です。回復()が終了すると、血液添加スレッドがこれを解放し、血液還元スレッドがこれを再占有して、その後の血液還元作業を実行できます。

使ç¨waitånotifyè¿è¡çº¿ç¨äº¤äº

public class InteractionTest {

	public static void main(String[] args) {

		final LOLHero gareen = new LOLHero();
		gareen.name = "盖伦";
		gareen.hp = 616;

		Thread t1 = new Thread() {
			public void run() {
				while (true) {

					gareen.hurt();

					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

			}
		};
		t1.start();

		Thread t2 = new Thread() {
			public void run() {
				while (true) {
					gareen.recover();

					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

			}
		};
		t2.start();

	}

}

class LOLHero {
	public String name;
	public float hp;

	public int damage;

	public synchronized void recover() {
		hp = hp + 1;
		System.out.printf("%s 回血1点,增加血后,%s的血量是%.0f%n", name, name, hp);
		// 通知那些等待在this对象上的线程,可以醒过来了,如等待着的减血线程,苏醒过来
		this.notify();    //
	}

	public synchronized void hurt() {
		if (hp == 1) {
			try {
				// 让占有this的减血线程,暂时释放对this的占有,并等待
				this.wait();    
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		hp = hp - 1;
		System.out.printf("%s 减血1点,减少血后,%s的血量是%.0f%n", name, name, hp);
	}

	public void attackHero(LOLHero h) {
		h.hp -= damage;
		System.out.format("%s 正在攻击 %s, %s的血变成了 %.0f%n", name, h.name, h.name, h.hp);
		if (h.isDead())
			System.out.println(h.name + "死了!");
	}

	public boolean isDead() {
		return 0 >= hp ? true : false;
	}

}

[3]、待機、通知、notifyAllについて

wait()メソッドとnotify()メソッドがオンになっているオブジェクトに注意してください。
 

public synchronized void hurt() {

    。。。
    this.wait();
    。。。

}
public synchronized void recover() {

    。。。
    this.notify();

}

ここで強調する必要があるのは、waitメソッドとnotifyメソッドはThreadスレッドのメソッドではなく、Objectのメソッドであるということです。 

すべてのオブジェクトを同期オブジェクトとして使用できるため、正確には、待機と通知は同期オブジェクトのメソッドです。

方法 意味
待つ() この同期オブジェクトを占有しているスレッドが一時的に現在の占有を解放して待機します。したがって、waitを呼び出すための前提条件があり、それは同期ブロック内にある必要があります。そうでない場合、エラーが発生します。
notify() この同期オブジェクトを待機しているスレッドに、ウェイクアップして現在のオブジェクトを再占有できることを通知します。
notifyAll() この同期オブジェクトを待機しているすべてのスレッドに、ウェイクアップして現在のオブジェクトを再占有できることを通知します。

(7)、スレッドプール

各スレッドの開始と終了は、時間とリソースを大量に消費します。 
システムで多数のスレッドが使用されている場合、開始アクションと終了アクションが多数あると、システムのパフォーマンスが低下し、応答が遅くなります。 
この問題を解決するために、スレッドプールの設計アイデアが導入されています。 
スレッドプールモデルはプロデューサーコンシューマーモデルと非常によく似ており、消費されるオブジェクトは1つずつ実行できるタスクです。

[1]、スレッドプールの設計アイデア

スレッドプールのアイデアは、プロデューサーコンシューマーモデルに非常に近いものです。
①タスクコンテナを準備する
②一度に10個のコンシューマスレッドを起動する
③タスクコンテナは最初は空なので、スレッドはすべて待機しています。
④外部スレッドがタスクコンテナに「タスク」をスローするまで、コンシューマスレッドが起動して通知します
。⑤このコンシューマスレッドは「タスク」を取り出してタスクを実行します。実行が完了すると、次の待機を続けます。ミッションが来ています。
⑥短時間でタスクが追加されると、複数のスレッドが起動してこれらのタスクを実行します。

プロセス全体で、新しいスレッドを作成する必要はありませんが、これらの既存のスレッドをリサイクルする必要があります

线ç¨æ±è®¾è®¡æè・¯

[2]、カスタムスレッドプールを開発する

import java.util.LinkedList;

public class ThreadPoolTest {

	public static void main(String[] args) {
		
		// 线程池
		ThreadPool pool = new ThreadPool();

		for (int i = 0; i < 7; i++) {	// 循环发布任务
			
			Runnable task = new Runnable() {
				@Override
				public void run() {
					// System.out.println("执行任务");
					// 任务可能是打印一句话
					// 可能是访问文件
					// 可能是做排序
				}
			};

			pool.add(task);	// 向线程池中发布任务

			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

	}

}

class ThreadPool {

	// 线程池大小
	int threadPoolSize;

	// 任务容器
	LinkedList<Runnable> tasks = new LinkedList<Runnable>();

	// 试图消费任务的线程

	public ThreadPool() {
		threadPoolSize = 10;

		// 启动10个任务消费者线程
		synchronized (tasks) {
			for (int i = 0; i < threadPoolSize; i++) {
				new TaskConsumeThread("任务消费者线程 " + i).start();
			}
		}
	}

	// 向线程池中发布任务
	public void add(Runnable r) {
		synchronized (tasks) {
			tasks.add(r);
			// 唤醒等待的任务消费者线程
			tasks.notifyAll();
		}
	}

	class TaskConsumeThread extends Thread {
		public TaskConsumeThread(String name) {
			super(name);
		}

		Runnable task;

		public void run() {
			System.out.println("启动: " + this.getName());
			while (true) {
				synchronized (tasks) {
					while (tasks.isEmpty()) {
						try {
							tasks.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					task = tasks.removeLast();
					// 允许添加任务的线程可以继续添加任务
					tasks.notifyAll();

				}
				System.out.println(this.getName() + " 获取到任务,并执行");
				task.run();
			}
		}
	}

}

結果:

[3]、java独自のスレッドプールを使用する

スレッドプールクラスThreadPoolExecutorは、パッケージjava.util.concurrentの下にあります。

ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

最初のパラメーター10は、このスレッドプールが10スレッドを初期化してそこで動作することを
意味します。2番目のパラメーター15は、10スレッドでは不十分な場合、自動的に最大15スレッドに増加することを意味し
ます。3番目のパラメーター60は4番目のパラメーターと組み合わされます。 TimeUnit.SECONDSは、60秒後、追加のスレッドが作業を受け取らなかった場合に再利用されることを意味します。最後に、プールには10個しかありません。4
番目のパラメーターTimeUnit.SECONDSは、上記の
5番目のパラメーターnew LinkedBlockingQueue()と同じです。タスクのセット

executeメソッドは、新しいタスクを追加するために使用されます

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorTest {

	static int sum = 0;

	public static void main(String[] args) throws InterruptedException {

		ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

		for (int i = 0; i < 30; i++) {

			int n = threadPool.getCorePoolSize();
			
			threadPool.execute(new Runnable() {

				@Override
				public void run() {
					System.out.println("任务" + this.hashCode()%1000 + "正在运行......此时线程池内线程数为:" + n);
				}

			});

		}

	}

}

結果:

(8)、ロックオブジェクト

同期と同様に、ロックも同期の効果を実現できます

[1]、ロックオブジェクトを使用して同期効果を実現します

Lockはインターフェースです。Lockオブジェクトを使用するには、次を使用する必要があります。

Lock lock = new ReentrantLock();


同期(someObject)と同様に、lock()メソッドは、現在のスレッドがロックオブジェクトを占有することを意味します。占有されると、他のスレッドはそれを占有できなくなります。
同期との違いは、同期されたブロックが終了すると、someObjectの占有が自動的に解放されることです。ロックは、unlockメソッドを呼び出して手動で解放する必要があります。解放を確実に実行するために、unlock()がfinalに配置されることがよくあります。

[2]、trylockメソッド

Synchronizedは休むことなく占有されることはなく、常にそれを占有しようとします。
同期方式とは異なり、Lockインターフェイスはtrylock方式も提供します。
Trylockは指定された時間範囲内で占有しようとします。アカウントが成功すると、スナップします。時間切れで占領に失敗した場合は、頭を回して立ち去って

ください注:トライロックは成功または失敗する可能性があるため、後でロックを解除するときは、占領が成功したかどうかを判断する必要があります。占領が成功しなかった場合は、ロックを解除します。例外をスローする

[3]、スレッドの相互作用

スレッドの相互作用には同期メソッドを使用します。同期オブジェクトのwait、notify、notifyAllメソッドが使用されます

。Lockも同様のソリューションを提供します。最初にlockオブジェクトを介してConditionオブジェクトを取得し、次にConditionオブジェクトを呼び出します:await、signal、 signalAllメソッド

注:Conditionオブジェクトのwait、nofity、notifyAllメソッドではなく、await、signal、signalAll

[4]、Lock和synchronized的区别

1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现。

2. Lock可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized不行,会一根筋一直获取下去。 借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生。

3. synchronized在发生异常和同步块结束的时候,会自动释放锁。而Lock必须手动释放, 所以如果忘记了释放锁,一样会造成死锁。

(9)、アトミックアクセス

アトミック操作は中断できない操作です

JDK6の後に、AtomicIntegerなどのさまざまなアトミッククラスを含む新しいパッケージjava.util.concurrent.atomicが追加されました。
また、AtomicIntegerは、すべてアトミックであるさまざまな自己インクリメントおよび自己デクリメントメソッドを提供します。つまり、incrementAndGetメソッドはスレッドセーフです。一度にこのメソッドを呼び出すことができるのは1つのスレッドだけです。

おすすめ

転載: blog.csdn.net/qq_37164975/article/details/82756233