一、生产者-消费者模型介绍
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队
列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从
阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三
者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟
悉一个设计模式。
这次的实现主要利用三个Object类下的方法:
1.1 wait()
其实wait()方法就是使线程停止运行。
- 方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法是用来将当前线程置
入“预执行队列”中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。 - wait()方法只能在同步方法中或同步块中调用。如果调用wait()时,没有持有适当的锁,会抛出异常。
- wait()方法执行后,当前线程释放锁,线程与其它线程竞争重新获取锁。
1.2 notify()
notify方法就是使停止的线程继续运行。
- 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其
发出通知notify,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑选出一个呈
wait状态的线程。 - 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出
同步代码块之后才会释放对象锁。
1.3 notifyAll()
notify方法只是唤醒某一个等待线程,那么如果有多个线程都在等待中怎么办呢,这个时候就可以使用notifyAll方法可以一次唤醒所有的等待线程.
1.4 三种方法的使用示例
package thread.method;
/**
* @author ellen
* @date 2019-11-28 7:10
* @description:wait()\notify()\notifyAll()
*/
public class MyThread implements Runnable {
private Object obj = new Object();
private boolean flag;
public MyThread(Object obj, boolean flag) {
this.obj = obj;
this.flag = flag;
}
public void waitMethod() {
synchronized (obj) {
System.out.println("wait start.." + Thread.currentThread().getName());
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait end.." + Thread.currentThread().getName());
}
}
public void notifyMethod() {
synchronized (obj) {
System.out.println("notify start.." + Thread.currentThread().getName());
// 唤醒任意一个等待的线程
// obj.notify();
// 唤醒所有等待的线程
obj.notifyAll();
System.out.println("notify end.." + Thread.currentThread().getName());
}
}
@Override
public void run() {
if(flag){
waitMethod();
}else{
notifyMethod();
}
}
public static void main(String[] args) {
Object o = new Object();
MyThread th1 = new MyThread(o,true);
MyThread th2 = new MyThread(o,false);
for (int i = 0; i < 10; i++) {
Thread threadi = new Thread(th1, "等待线程" + i);
threadi.start();
}
Thread thread = new Thread(th2, "唤醒线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.start();
}
}
1.5 补充说明:出现阻塞的情况
出现阻塞的情况大体分为如下5种:
① 线程调用 sleep方法,主动放弃占用的处理器资源。
② 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
③ 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
④ 线程等待某个通知
⑤ 程序调用了 suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法。
run()方法运行结束后进入销毁阶段,整个线程执行完毕。
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。
二、实现
生产者与消费者一般需要第三者来解耦的,所以现在就模拟一个简单的商品的生产者与消费者,由生产者线程生产出一个商品之后将由消费者线程开始消费!
2.1 基本实现
1、首先创建一个抽象的商品类Goods,类中有商品库存以及生产&消费方法。
public abstract class Goods {
private String goodsName;//商品名称
private int count;//库存
public int getCount() {
return count;
}
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public void setCount(int count) {
this.count = count;
}
abstract void set(String goodsName);
abstract void get();
@Override
public String toString() {
return "Goods{ " +
"goodsName = '" + goodsName + '\'' +
" }";
}
}
2、具体实现类:
public class Goods1 extends Goods {
// 生产商品
@Override
synchronized void set(String goodsName) {
this.setGoodsName(goodsName);
this.setCount(this.getCount() + 1);
System.out.println(Thread.currentThread().getName() + "生产了 " + toString() + this.getCount());
}
// 消费商品
@Override
synchronized void get() {
this.setCount(this.getCount() - 1);
System.out.println(Thread.currentThread().getName() + "消费了 " + toString() + this.getCount());
}
}
3、然后创建生产者和消费者类
(1)消费者类
public class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
goods.get();
}
}
(2)生产者类
public class Producer implements Runnable {
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
goods.set(" iphone ");
}
}
4、测试类
public class Test {
public static void main(String[] args) {
Goods goods = new Goods1();
Thread productThread = new Thread(new Producer(goods), "生产者1 ");
Thread consumeThread = new Thread(new Consumer(goods), "消费者1 ");
// 有问题:
productThread.start();
consumeThread.start();
}
}
2.2 改进
1、多个生产者线程和消费者线程
2、生产商品数量为10后不再生产,直到商品数为0
(1)商品实现类
public class Goods4 extends Goods {
// 生产商品
public synchronized void set(String goodsName){
this.setGoodsName(goodsName);
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setCount(this.getCount() + 1);
System.out.println(Thread.currentThread().getName() + "生产了 " + toString() + this.getCount());
// 唤醒等待消费的线程
notifyAll();
}
// 消费商品
public synchronized void get(){
while(this.getCount() == 0){
System.out.println("该商品库存不足,客官稍等一下~~");
// 等待商品生产
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setCount(this.getCount() - 1);
System.out.println(Thread.currentThread().getName() + "消费了 " + toString() + this.getCount());
// 唤醒生产者继续生产
notifyAll();
}
}
(2)生产者类
public class Producer implements Runnable {
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while(goods.getCount() < 10){
goods.set(" iphone ");
}
}
}
(3)消费者类
public class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while(true){
goods.get();
}
}
}
(4)测试类
public class Test {
public static void main(String[] args) {
Goods goods = new Goods4();
List<Thread> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
Thread cosumer = new Thread(new Consumer(goods), "消费者" + i);
list.add(cosumer);
}
for (int i = 1; i <= 5; i++) {
Thread producer = new Thread(new Producer(goods), "生产者" + i);
list.add(producer);
}
for(Thread th : list){
th.start();
}
}
}