多线程学习-day-04等待(wait)和通知(notify/notifyAll)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Xgx120413/article/details/83065835

线程基础、线程之间的共享和协作

(目前会将一些概念简单描述,一些重点的点会详细描述)
学习目标:等待和通知

一、应用场景:

一个线程修改了一个值,另一个线程感受到了值的变化,进行相应的操作。前一个线程类比于一个生产者,后一个线程是消费者。如何让消费者感受到生产者的一个值的变化呢?

解决方案一:

轮询:每隔一分钟就去轮询一次,总有一个时间点能够获取到生产者的变换。比如煲汤,每个一分钟就去看一下是否煲好了。结果:这样会很累,很占用资源。

轮询的缺点:很难确保一个及时性,每隔一段时间就要去操作一次,资源开销很大,做很多无用功。

解决方案二:

等待和通知机制方式:当一个线程调用了wait()方法,会进入一个等待状态,而另外一个线程对值进行操作后,调用notify()或者notifyAll()方法后,通知第一个线程去操作某件事情。注意:wait()、notify()/notifyAll()是对象上的方法。

wait()等待方会怎么做?

1、获取对象的锁;一定是在循环里面去操作;

2、循环里判断是否满足条件,不满足条件调用wait()方法,一直等待;

3、满足条件,执行业务逻辑;

notify()、notifyAll()会怎么做?

1、依然要获取对象的锁;

2、改变相关条件;

3、通知所有等待在对象的线程

以上介绍了wait、notify/notifyAll的标准范式。

三、notify()和notifyAll()区别:

应该尽量应用notifyAll(),使用notify()的话,jvm会执行已经加入等待线程栈里面的第一个线程,给我们一种感观就是随机的选择了一种线程,如果该线程达到条件就正好执行那一条,其实这是一个误区,而是jvm会选择在线程栈里面的第一个线程。因此如果用notify()的话,可能会造成信号丢失的情况。

举例应用:比如一个快递,发货地址是长沙,收货地址是深圳,初始发货公里(km)为0。则改变公里数(km)以及改变收货地址,来操作等待/通知的情景

先定义一个Express类

public class Express {
	// 定义一个发货地,这里定为长沙
	public static final String CITY = "ChangSha";
	// 定义一个千米数,表示货已经走了多少千米了
	private int km;
	// 定义一个发货城市,表示货从哪个城市出发
	private String site;

	// 定义无参构造方法
	public Express() {
	}

	// 定义有参构造方法
	public Express(int km, String site) {
		this.km = km;
		this.site = site;
	}

	// 定义改变km的方法
	public synchronized void changeKm() {
		// 定义已经走了101千米了
		km = 101;
		// 千米数改变之后,就发送通知
		notifyAll();
	}

	// 定义改变城市的方法
	public synchronized void changeSite() {
		// 定义深圳收货
		site = "ShenZhen";
		// 城市改变后,就发送通知
		notifyAll();
	}

	// 定义千米等待的方法,等待km改变之后,就取消等待,输出结果
	public synchronized void waitKm() {
		// 当km小于100时,还没有超过100km,则继续等待超过100km后的通知
		while (this.km <= 100) {
			try {
				wait();
				System.out.println("当前线程:" + Thread.currentThread().getId() + " 还没有运送超过100km,还在等通知!");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// 收到通知后,等待结束,输出结果
		System.out.println("当前km = " + this.km + " 已经运送超过100km了,进行数据库存储操作!");
	}

	// 定义城市等待的方法,等待城市改变之后,就取消等待,输出结果
	public synchronized void waitSite() {
		// 当site还没有到长沙,则继续等待到长沙之后的通知
		while (CITY.equals(this.site)) {
			try {
				wait();
				System.out.println("当前线程:" + Thread.currentThread().getId() + " 还没有到达长沙,还在等通知!");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// 收到通知后,表示到达长沙,等待结束,输出结果
		System.out.println("当前site = " + this.site + "已经到达深圳,要告诉用户下来拿快递!");
	}
}

定义测试类TestWN(Test Wait Notifyall缩写)

public class TestWN {

	// 实例化Express对象,并调用对象锁
	private static Express express = new Express(0, Express.CITY);

	// 定义方法,检查km变化情况,没有达到条件一直等待
	public static class ThreadKm extends Thread {
		@Override
		public void run() {
			// 因为Express里面的方法都是对象锁,所以直接调用
			express.waitKm();
		}
	}

	// 定义方法,检查site变化情况,没有达到条件一直等待
	public static class ThreadSite extends Thread {
		@Override
		public void run() {
			// 因为Express里面的方法都是对象锁,所以直接调用
			express.waitSite();
		}
	}

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

		// 设置site变化
		for (int i = 0; i < 3; i++) {
			new ThreadSite().start();
		}

		// 设置km变化
		for (int i = 0; i < 3; i++) {
			new ThreadKm().start();
		}

		// 休眠3秒,然后改变里程数
		Thread.sleep(1000);
		express.changeKm();
		express.changeSite();
	}
}

控制台输出结果:
当前线程:15 还没有运送超过100km,还在等通知!
当前km = 101 已经运送超过100km了,进行数据库存储操作!
当前线程:14 还没有运送超过100km,还在等通知!
当前km = 101 已经运送超过100km了,进行数据库存储操作!
当前线程:13 还没有运送超过100km,还在等通知!
当前km = 101 已经运送超过100km了,进行数据库存储操作!
当前线程:12 还没有到达长沙,还在等通知!
当前site = ShenZhen已经到达深圳,要告诉用户下来拿快递!
当前线程:11 还没有到达长沙,还在等通知!
当前site = ShenZhen已经到达深圳,要告诉用户下来拿快递!
当前线程:10 还没有到达长沙,还在等通知!
当前site = ShenZhen已经到达深圳,要告诉用户下来拿快递!

以上介绍的就是等待/通知标准范式的代码演示。

 

join()方法

线程A,执行了线程B的join方法,线程A必须要等待B执行完成了以后,线程A才能继续自己的工作

调用yield() sleep()wait()notify()等方法对锁有何影响? 

线程在执行yield()以后,持有的锁是不释放的

sleep()方法被调用以后,持有的锁是不释放的

调动方法之前,必须要持有锁。调用了wait()方法以后,锁就会被释放,当wait方法返回的时候,线程会重新持有锁

调动方法之前,必须要持有锁,调用notify()方法本身不会释放锁的

来自享学IT教育课后总结。

猜你喜欢

转载自blog.csdn.net/Xgx120413/article/details/83065835