1.线程通信概述
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
生产者与消费者问题描述了两个或者多个共享固定大小缓冲区的线程–即所谓的"生产者"和"消费者",在实际运行中发生的问题。
生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区空时消耗数据。
生产者与消费者问题中隐含两个问题
1.线程安全问题:因为生产者与消费者共享数据缓冲区,这个问题可以通过同步解决。
2.线程的协调工作问题:要解决就这样的问题,就必须让生产者线程在缓冲区满时等待(wait),暂停进入阻塞状态,等待下一次消费者消耗了缓冲区中的数据的时候,通知(notify)正在等待的线程恢复到就绪状态,重新开始往缓冲区添加数据。同样,也可以让消费者线程在缓冲区空时进入等待(wait),暂停进入阻塞状态,等到生产者往缓冲区添加数据之后,再通知(notify)正在等待的线程恢复到就绪状态。通过这样的通信机制来解决这样的问题。
注意:Object类中提供了wait()、notify()、notifyAll()方法,这三个方法并不属于Thread类,那是因为这三个方法必须有同步监视器对象来进行调用,而同步监视器对象可以是任意类型的对象,因此它们只能声明在Object类中。
为什么要处理线程间通信: 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们 希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源: 多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就 是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效 的利用资源。而这种手段即—— 等待唤醒机制。
2 .等待唤醒机制
什么是等待唤醒机制 ?
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是 故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时 候你们更多是一起合作以完成某些任务。
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将 其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。 wait/notify 就是线程间的一种协作机制。
当不同种类的线程之间需要通信时,这里用生产者与消费者问题进行详细阐述。
- 如果生产线程,它生产出来资源,有了资源,应该等着通知消费者线程来进行消费
- 如果消费线程,它进行消耗资源,没有资源,应该等着通知生产线程进行生产
上图说明的现象是线程之间的等待唤醒机制。
首先要模拟线程之间的等待唤醒机制(生产者与消费者问题)需要的条件为:生产者线程(SetThread)、消费者线程(GetThread)、共享资源。
出现数据安全问题的条件:1.是不是多线程环境 2.是否多线程共享资源 3.是否有多条语句在操作这个共享资源。
生产者与消费者问题的需求是 | 要的效果为生产一个,消费一个。 |
生产者 | 如果没有资源就生产,如果有资源,就等着不生产,还得通知消费线程去消费。 |
消费者 | 如果有资源,就消费,如果没有资源就等着,还得通知生产线程去生产。 |
Object类中为等待唤醒机制提供的方法 | 方法说明 |
void wait () | 在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。 |
void wait (long timeout) | 在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,或者超过指定的时间量前,导致当前线程等待。 |
void notify () | 唤醒在此对象监视器上等待的单个线程。 |
void notifyAll () | 唤醒在此对象监视器上等待的所有线程。 |
调用wait和notify方法需要注意的细节:
1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对 象调用的wait方法后的线程。
2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继 承了Object类的。
3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方 法。
3:生产者与消费者
线程的等待唤醒机制的简单实例问题演示:
包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子 (即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。 接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包 子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取 决于锁的获取情况。
代码演示:
1:创建一个包子类,并给包子两个属性(名字 ,状态)
/**
* 现成的共有资源对象----包子
*/
public class BaoZi {
String name;
boolean flag;
}
2:创建两个线程----(1)早餐店制作包子的线程 (2)吃货的线程
早餐店
package Test04;
public class ZaoCanDian extends Thread{
//资源对象
BaoZi baoZi;
//定义构造方法
public ZaoCanDian(String threadName ,BaoZi bz){
super(threadName);
this.baoZi=bz;
}
/*
* 早餐店线程功能:
* 如果包子存在, 线程进入等待状态
* 如果包子不存在, 线程开始制作包子,制作完毕更改包子状态为存在,唤醒吃货线程吃包子
* */
@Override
public void run(){
//获取线程名字
String threadName = Thread.currentThread().getName();
for (int i = 0;i<10;i++){
synchronized (baoZi){
if (baoZi.flag){//包子存在
try{
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{//包子不存在
System.out.println(threadName+"制作"+baoZi.name);//制造包子
baoZi.flag=true;//更改包子状态
baoZi.notify();//唤醒同一资源下的其他线程
}
}
}
}
}
吃货
package Test04;
public class ChiHuo extends Thread{
//资源对象
BaoZi baoZi;
//定义构造方法:给线程定义名字,同时给BaoZi对象赋值
public ChiHuo(String threadName,BaoZi bz){
super(threadName);
this.baoZi=bz;
}
/*
* 吃货线程的功能:
* 如果包子不存在, 线程进入等待状态
* 如果包子不存在, 线程开始吃包子,吃完后更改包子的状态变为不存在,唤醒早餐店线程开始制作包子
* */
@Override
public void run() {
//获取线程的名字
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
synchronized (baoZi) {
if (baoZi.flag) {//包子存在
System.out.println(threadName + "正在吃" + baoZi.name); //吃包子
baoZi.flag = false;//更改包子状态
baoZi.notify();//唤醒同一资源下的其他线程
} else {//包子不存在
try {
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
测试类:
package Test04;
public class ThreadTest01 {
public static void main(String[] args) {
BaoZi bz=new BaoZi();
bz.name="韭菜鸡蛋";
bz.flag=false;
ChiHuo ch=new ChiHuo("猪八戒",bz);
ZaoCanDian zcd=new ZaoCanDian("高村姑早餐店",bz);
ch.start();
zcd.start();
}
}
执行效果: