等待唤醒机制是多线程通信中的概念,首先我们要先了解什么是线程间的通信
一、多线程间的通信
概念:多个线程都在处理同一个资源,但是处理的任务不一样。此时就需要多线程之间进行通信,保证同一资源的正确性。
剖析:通过多线程间的通信的经典实例来进一步了解多线程间通信的概念
经典实例:生产者,消费者
二、生产者,消费者
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();
}
}