等待唤醒机制

等待唤醒机制是多线程通信中的概念,首先我们要先了解什么是线程间的通信

一、多线程间的通信

概念:多个线程都在处理同一个资源,但是处理的任务不一样。此时就需要多线程之间进行通信,保证同一资源的正确性。

剖析:通过多线程间的通信的经典实例来进一步了解多线程间通信的概念

经典实例:生产者,消费者

二、生产者,消费者

1、首先通过一个简单的示意图来了解生产者和消费者的概念


2、用代码实例进一步说明


需求:生产者生产一个面包后,放入容器,只有当消费者消费后,生产者才能开始生产,以此类推。


根据图示可分析生成者和消费者是两个不同的任务,而操作的确是相同的资源,所以必须进行通信。

首先建立生产者、消费者、资源三个类来描述事物。

/**
 * @author zqx
 *	描述资源
 */
public class Resource {
	private String name;
	private int count = 1;

	// 提供给消费者获取商品的方法
	public void get() {
		System.out.println(Thread.currentThread().getName() + "........消费者....." + this.name);
	}

	// 提供给生产者生产商品的方法
	public void setName(String name) {
		this.name = name + "--" + count;
		count++;
		System.out.println(Thread.currentThread().getName() + ".....生产者....." + this.name);
	}
}
/**
 * @author zqx 生产者任务
 */
public class Producer implements Runnable {
<span style="white-space:pre">	</span>private Resource resource;


<span style="white-space:pre">	</span>Producer(Resource resource) {
<span style="white-space:pre">		</span>this.resource = resource;
<span style="white-space:pre">	</span>}


<span style="white-space:pre">	</span>@Override
<span style="white-space:pre">	</span>public void run() {
<span style="white-space:pre">		</span>while (true)
<span style="white-space:pre">			</span>resource.set("面包");
<span style="white-space:pre">	</span>}
}

</pre><pre name="code" class="java">/**
 * @author zqx 消费者任务
 */
public class Custemer implements Runnable {
	private Resource resource;

	Custemer(Resource resource) {
		this.resource = resource;
	}

	@Override
	public void run() {
		while (true)
			resource.get();
	}
}


/**
 * @author zqx
 *	测试类
 */
public class ProducerCustemer {
	public static void main(String[] args) {
		//1、创建资源对象
		Resource resource = new Resource();
		//2、创建两个线程,构造函数的方式初始化两个线程,保证操作同一资源
		Producer producer = new Producer(resource);
		Custemer custemer = new Custemer(resource);
		//3、创建线程
		Thread t1 = new Thread(producer);
		Thread t2 = new Thread(custemer);
		//4、开启线程
		t1.start();
		t2.start();
	}
}

由于线程运行的随机性,程序将会出现以下安全问题:

1、生产者没生产出面包时,消费者已经消费该面包。 生产消费顺序问题。

2、生产者生产完毕后,消费者没有消费之前,生产者又生产了。

3、消费者连续消费同一面包多次。

如果要解决问题1,可以使用同步。在这里选择使用同步函数的方式解决。但是加入同步后问题2和问题3依然存在。

如果要解决问题2和问题3,就要用到多线程中的等待唤醒机制。那么什么是等待唤醒机制呢?


三、等待唤醒机制

1、分析:

首先分析出现问题2和问题3的原因,可参照上文中的图片进行理解。

加入同步后,生产者拿到锁之后生产面包,消费者处于临时阻塞状态,当生产者释放锁的时候,由于CPU执行的随机性,生产者和消费者都有可能拿到锁。如果生产者再次拿到锁就会出现安全问题。如何解决?

2、总结:

定义一个布尔类型flag标记容器中是否有面包。生产和消费前首先判断flag的值,如果为flag为false,则冻结消费者线程,如果flag为true,则冻结生产者。初始化flag的值为false ,生产者生产面包后,把flag的值置为true,并唤醒消费者 ; 当消费者消费完面包时,把flag的值置为false,并唤醒生产者。这就是多线程的等待唤醒机制

冻结:wait():该方法可以让线程处于冻结状态,并将线程临时存储到线程池中。

唤醒:notify():唤醒指定线程池中的任意一个线程。只唤醒一个。

    notifyAll():唤醒指定线程池中的所有线程



上述概念用简图来表示 :


3、完整代码:

public class Resource {
	private String name;
	private int count = 1;
	// 定义flag标记
	private boolean flag;

	// 提供给消费者获取商品的方法

	public synchronized void get() {
		if (!flag) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			System.out.println(Thread.currentThread().getName() + "........消费者....." + this.name);
			// 将标记置为false
			flag = false;
			// 唤醒生产者
			notify();
		}
	}

	// 提供给生产者生产商品的方法
	public synchronized void set(String name) {
		// 如果falg为true,则说明已经有面包,执行等待,如果flag为false,执行生产。
		if (flag) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			this.name = name + "--" + count;
			count++;
			System.out.println(Thread.currentThread().getName() + ".....生产者....." + this.name);
			// 生产完毕,将标记置为true,并唤醒消费者
			flag = true;
			notify();
		}
	}
}
生产者任务
<pre name="code" class="java">public class Producer implements Runnable {
	private Resource resource;

	Producer(Resource resource) {
		this.resource = resource;
	}

	@Override
	public void run() {
		while (true)
			resource.set("面包");
	}
}
 
 
public class Custemer implements Runnable {
	private Resource resource;

	Custemer(Resource resource) {
		this.resource = resource;
	}

	@Override
	public void run() {
		while (true)
			resource.get();
	}
}
<pre name="code" class="java">public class ProducerCustemer {
	public static void main(String[] args) {
		//1、创建资源对象
		Resource resource = new Resource();
		//2、创建两个线程,构造函数的方式初始化两个线程,保证操作同一资源
		Producer producer = new Producer(resource);
		Custemer custemer = new Custemer(resource);
		//3、创建线程
		Thread t1 = new Thread(producer);
		Thread t2 = new Thread(custemer);
		//4、开启线程
		t1.start();
		t2.start();
	}
}


 
 

四、等待唤醒机制原理

1、wait()、notify() 这样的方法必须用在同步当中,离开同步是没有意义的。原因:wait() notity()这样的方法本身就是用来操作同步锁上的线程的状态的,锁也称之为监视器。
在使用这些方法时,必须标识它们所属的锁,标识方式就是 锁对象.wait();锁对象.notify(); 锁对象.notifyAll();
相同锁的notify(),可以唤醒相同锁的wait();

上述例子中使用的锁是this


通过查阅API,会发现wait()这种方法定义在Objec类t当中,而没定义在Thread类中,就是因为 锁对象是任意对象。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值