生产者消费者模型
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式。
在正式介绍生产者与消费者模型之前,我们先熟悉一下wait()、notify()、notufyAll()方法。
1、wait()方法
其实wait()方法就是使线程停止运行。
方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法是用来将当前线程置入“预执行队列”中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。
wait()方法只能在同步方法中或同步块中调用。如果调用wait()时,没有持有适当的锁,会抛出异常。
wait()方法执行后,当前线程释放锁,线程与其它线程竞争重新获取锁。
例:使用wait()
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object) {
System.out.println("等待中...");
object.wait();
System.out.println("等待已过...");
}
System.out.println("main方法结束...");
}
这样在执行到object.wait()之后就一直等待下去,那么程序肯定不能一直这么等待下去了。这个时候就需要使用到了另外一个方法唤醒的方法notify()。
2、notify()方法
notify方法就是使停止的线程继续运行。
方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑选出一个呈wait状态的线程。
在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
例:使用notify()方法唤醒线程
package bit.java.thread;
class MyThread implements Runnable {
private boolean flag;
private Object obj;
public MyThread(boolean flag, Object obj) {
super();
this.flag = flag;
this.obj = obj;
}
public void waitMethod() {
synchronized (obj) {
try {
while (true) {
System.out.println("wait()方法开始.. " + Thread.currentThread().getName());
obj.wait();
System.out.println("wait()方法结束.. " + Thread.currentThread().getName());
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void notifyMethod() {
synchronized (obj) {
try {
System.out.println("notify()方法开始.. " + Thread.currentThread().getName());
obj.notify();
System.out.println("notify()方法结束.. " + Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
if (flag) {
this.waitMethod();
} else {
this.notifyMethod();
}
}
}
public class TestThread {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
MyThread waitThread = new MyThread(true, object);
MyThread notifyThread = new MyThread(false, object);
Thread thread1 = new Thread(waitThread, "wait线程");
Thread thread2 = new Thread(notifyThread, "notify线程");
thread1.start();
Thread.sleep(1000);
thread2.start();
System.out.println("main方法结束!!");
}
}
从结果上来看第一个线程执行的是一个waitMethod方法,该方法里面有个死循环并且使用了wait方法进入等待状态将释放锁,如果这个线程不被唤醒的话将会一直等待下去,这个时候第二个线程执行的是notifyMethod方法,该方法里面执行了一个唤醒线程的操作,并且一直将notify的同步代码块执行完毕之后才会释放锁然后继续执行wait结束打印语句。
注意:wait,notify必须使用在synchronized同步方法或者代码块内。
3、在介绍一下notifyAll()方法
以上讲解了notify方法只是唤醒某一个等待线程,那么如果有多个线程都在等待中怎么办呢,这个时候就可以使用notifyAll方法可以一次唤醒所有的等待线程,看示例。
例:使用notifyAll()方法唤醒所有等待线程
package bit.java.thread;
class MyThread implements Runnable {
private boolean flag;
private Object obj;
public MyThread(boolean flag, Object obj) {
super();
this.flag = flag;
this.obj = obj;
}
public void waitMethod() {
synchronized (obj) {
try {
while (true) {
System.out.println("wait()方法开始.. " + Thread.currentThread().getName());
obj.wait();
System.out.println("wait()方法结束.. " + Thread.currentThread().getName());
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void notifyMethod() {
synchronized (obj) {
try {
System.out.println("notifyAll()方法开始.. " + Thread.currentThread().getName());
obj.notifyAll();
System.out.println("notifyAll()方法结束.. " + Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
if (flag) {
this.waitMethod();
} else {
this.notifyMethod();
}
}
}
public class TestThread {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
MyThread waitThread1 = new MyThread(true, object);
MyThread waitThread2 = new MyThread(true, object);
MyThread waitThread3 = new MyThread(true, object);
MyThread notifyThread = new MyThread(false, object);
Thread thread1 = new Thread(waitThread1, "wait线程A");
Thread thread2 = new Thread(waitThread2, "wait线程B");
Thread thread3 = new Thread(waitThread3, "wait线程C");
Thread thread4 = new Thread(notifyThread, "notify线程");
thread1.start();
thread2.start();
thread3.start();
Thread.sleep(1000);
thread4.start();
System.out.println("main方法结束!!");
}
}
现在可以看出notifyAll确实是唤醒了所有线程了。
注意:唤醒线程不能过早,如果在还没有线程在等待中时,过早的唤醒线程,这个时候就会出现先唤醒,在等待的效果了。这样就没有必要在去运行wait方法了。
总结一下:
出现阻塞的情况大体分为如下5种:
线程调用 sleep方法,主动放弃占用的处理器资源。
线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
线程等待某个通知。
程序调用了 suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法
run()方法运行结束后进入销毁阶段,整个线程执行完毕。
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。
好了,到这里该讲一下生产者与消费者模型
生产者与消费者开头已经介绍过了,生产者与消费者一般需要第三者来解耦的,所以现在就模拟一个简单的商品的生产者与消费者,由生产者线程生产出一个商品之后将由消费者线程开始消费!
首先创建一个商品类Goods,类中有商品库存以及生产&消费方法。
例:商品类Goods
class Goods {
// 商品名称
private String goodsName;
// 商品库存
private int count;
// 生产方法
public synchronized void set(String goodsName) {
this.goodsName = goodsName;
this.count = count+1;
System.out.println(toString());
}
// 消费方法
public synchronized void get() {
// 每次消费一个商品
this.count = this.count - 1;
System.out.println(toString());
}
@Override
public String toString() {
return "Goods [goodsName=" + goodsName + ", count=" + count + "]";
}
}
然后创建生产者和消费者类
例:消费者以及生产者类
/**
* 生产者类
* @author 38134
*
*/
class Producer implements Runnable {
private Goods goods;
public Producer(Goods goods) {
super();
this.goods = goods;
}
@Override
public void run() {
this.goods.set("奔驰C200L一辆");
}
}
/**
* 消费者类
* @author 38134
*
*/
class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
super();
this.goods = goods;
}
@Override
public void run() {
this.goods.get();
}
}
例:测试类
public static void main(String[] args) throws InterruptedException {
Goods goods = new Goods();
Thread produceThread = new Thread(new Producer(goods),"生产者线程");
Thread consumerThread = new Thread(new Consumer(goods),"消费者线程");
// 启动生产者线程
produceThread.start();
Thread.sleep(1000);
// 启动消费者线程
consumerThread.start();
}
注意:那么现在换一种方式,将生产者线程开启和消费者线程开启的代码换个位置在再测试下。此时问题产生了,生产者还没生产商品消费者就消费了导致数量不正确。此时就需要我们的wait()和notify()方法帮忙。
例:修改上述代码
/**
* 商品类
* @author 38134
*
*/
class Goods {
// 商品名称
private String goodsName;
// 商品库存
private int count;
// 生产方法
public synchronized void set(String goodsName) throws InterruptedException {
// 此时还有商品没被消费,等待消费者消费
if (this.count > 0) {
System.out.println("还有库存哦,等待消费者..");
wait();
}
this.goodsName = goodsName;
this.count = count+1;
Thread.sleep(1000);
System.out.println("生产"+toString());
// 生产完商品后通知消费者线程可以消费了
notify();
}
// 消费方法
public synchronized void get() throws InterruptedException {
// 此时还没有商品,等待生产者生产商品
if (this.count == 0) {
System.out.println("商品卖完了,客官等等哦..");
wait();
}
// 每次消费一个商品
this.count = this.count - 1;
Thread.sleep(1000);
System.out.println("消费"+toString());
// 消费完告知生产者线程可以继续生产了
notify();
}
@Override
public String toString() {
return "Goods [goodsName=" + goodsName + ", 库存为" + count + "]";
}
}
/**
* 生产者类
* @author 38134
*
*/
class Producer implements Runnable {
private Goods goods;
public Producer(Goods goods) {
super();
this.goods = goods;
}
@Override
public void run() {
try {
this.goods.set("奔驰C200L一辆");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 消费者类
* @author 38134
*
*/
class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
super();
this.goods = goods;
}
@Override
public void run() {
try {
this.goods.get();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Goods goods = new Goods();
T hread produceThread = new Thread(new Producer(goods),"生产者线程");
Thread consumerThread = new Thread(new Consumer(goods),"消费者线程");
// 启动消费者线程
consumerThread.start();
// 启动生产者线程
produceThread.start();
}
}
多生产以及多消费
以上只有一个生产者生产一次商品和一个消费者只消费一次就结束了,现在能否改变一下,多个生产者和多个消费者呢?这样的话我们该怎么改造代码呢?
分析一下,首先notify方法目前是只能唤醒一个线程,如果有多个生产者线程和多个消费者线程的话,这个notify方法唤醒的线程如果是消费者的话应该没有问题,但是如果是唤醒的也是生产者的线程那么程序就会变成假死状态了,这个时候显然这个notify方法不行,我们上一节讲过了有一个notifyAll()唤醒当前对象的所有线程。这个时候就可以使用该方法了,好了我们开始改造。
例:改造多生产
public synchronized void set(String goodsName) throws InterruptedException {
// 此时还有商品没被消费,等待消费者消费
while (this.count > 0) {
wait();
}
this.goodsName = goodsName;
this.count = count+1;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
System.out.println("生产"+toString());
System.out.println("============================================");
// 生产完商品后通知消费者线程可以消费了
notifyAll();
}
// 继续生产商品
@Override
public void run() {
while(true) {
try {
this.goods.set("奔驰C200L一辆");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
例:修改多消费
// 消费方法
public synchronized void get() throws InterruptedException {
// 此时还没有商品,等待生产者生产商品
while (this.count == 0) {
wait();
}
// 每次消费一个商品
this.count = this.count - 1;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
System.out.println("消费"+toString());
System.out.println("============================================");
// 消费完告知生产者线程可以继续生产了
notifyAll();
}
@Override
public void run() {
// 继续消费
while(true) {
try {
this.goods.get();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
例:修改测试类引入多个生产、消费者进程
public class Test {
public static void main(String[] args) throws InterruptedException {
Goods goods = new Goods();
// 存储生产、消费者线程
List<Thread> threadList = new ArrayList<>();
// 10个生产者线程
for (int i = 0; i < 10; i++) {
Thread produceThread = new Thread(new Producer(goods));
produceThread.setName("生产者线程 "+i);
threadList.add(produceThread);
}
// 6个消费者线程
for (int i = 0; i < 6; i++) {
Thread consumeThread = new Thread(new Consumer(goods));
consumeThread.setName("消费者线程 "+i);
threadList.add(consumeThread);
}
// 启动所有线程
for (Thread thread : threadList) {
thread.start();
}
}
}

本文详细介绍了生产者消费者模型的概念及实现方式,包括如何利用wait()、notify()和notifyAll()方法来协调生产者和消费者之间的同步问题。并通过具体代码示例展示了单生产和多消费场景下的应用。

被折叠的 条评论
为什么被折叠?



