生产者消费者模式是通过一个容器
来解决生产者和消费者的强耦合
问题。生产者和消费者彼此之间不直接通讯
,而通过阻塞队列
来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
1.wait()与notify()方法
1.1.wait()方法(痴汉)
- 方法wait()的作用是
使当前执行代码的线程立刻停止运行,处于等待状态
(wait),wait()方法是Object类的方法,该方法是用来将当前线程置入“预执行队列”中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止 - wait()方法只能在
同步方法中或同步块中调用
,如果调用wait()时,没有持有适当的锁(必须是内建锁),会抛出异常 - wait()方法执行后,当前线程释放锁,线程与其它线程竞争重新获取锁
痴汉方法1.0版本——死等,直到被唤醒或中断
public final void wait() throws InterruptedException
痴汉2.0——超时等待,若规定时间内没被唤醒则线程退出,单位毫秒
public final void wait(long timeout) throws InterruptedException
在上个方法的基础上增加了纳秒控制
public final void wait(long timeout) throws InterruptedException
wait()方法使用:
public class Test{
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()
1.2.notify()(痴汉等待的姑娘)
- 方法notify()也要
在synchronized同步方法或者代码块内调用
,用来唤醒等待该对象的其他线程,对其发出通知notify,并使它们重新获取该对象的对象锁。如果有多个线程等待,随机挑选一个被唤醒(抛绣球) - 在notify()方法后,当前线程
不会马上释放该对象锁
,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁
使用notify()方法唤醒线程示例:
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 Test {
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方法结束!!");
}
}
//wait()方法开始.. wait线程
//main方法结束!!
//notify()方法开始.. notify线程
//notify()方法结束.. notify线程
//wait()方法结束.. wait线程
从结果上来看第一个线程执行的是一个waitMethod方法,该方法里面有个死循环并且使用了wait方法进入等待状态将释放锁,如果这个线程不被唤醒的话将会一直等待下去,这个时候第二个线程执行的是notifyMethod方法,该方法里面执行了一个唤醒线程的操作,并且一直将notify的同步代码块执行完毕之后才会释放锁然后继续执行wait结束打印语句
!!!注意:wait,notify必须使用在synchronized同步方法或者代码块内
1.3.notifyAll()方法
notify方法只是唤醒某一个等待线程,那么如果有多个线程都在等待中怎么办呢,这个时候就可以使用notifyAll方法可以一次唤醒所有的等待线程,示例如下:
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 Test {
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方法结束!!");
}
}
//wait()方法开始.. wait线程
//main方法结束!!
//notify()方法开始.. notify线程
//notify()方法结束.. notify线程
//wait()方法结束.. wait线程
注意:唤醒线程不能过早,如果在还没有线程在等待中时,过早的唤醒线程,这个时候就会出现先唤醒,在等待的效果了。这样就没有必要在去运行wait方法了
1.4.小结
出现阻塞的情况
大体分为如下5种:
- 线程调用 sleep方法,主动放弃占用的处理器资源
- 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞
- 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有
- 线程等待某个通知
- 程序调用了 suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法
run()方法运行结束后进入销毁阶段,整个线程执行完毕。每个锁对象都有两个队列,一个是就绪队列
,一个是阻塞队列
。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒
2.生产者与消费者模型
2.1.生产者消费者模型
生产者与消费者一般需要第三者来解耦
的,所以现在就模拟一个简单的商品的生产者与消费者,由生产者线程生产出一个商品之后将由消费者线程开始消费
首先创建一个商品类Goods,类中有商品库存以及生产&消费方法,示例如下:
class Goods{
//商品名
private String goodName;
//商品库存
private int count;
//生产方法
public synchronized void set(String goodName){
this.goodName = goodName;
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{" + "goodName='" + goodName + '\'' + ", count=" + count + '}';
}
}
创建生产者消费者:
//生产者
class Producer implements Runnable{
private Goods good;
public Producer(Goods good) {
this.good = good;
}
@Override
public void run() {
this.good.set("红旗拖拉机一辆");
}
}
//消费者
class Consumer implements Runnable{
private Goods good;
public Consumer(Goods good) {
this.good = good;
}
@Override
public void run() {
this.good.get();
}
}
测试类:
public class Test{
public static void main(String[] args) throws InterruptedException {
Goods good = new Goods();
Thread produceThread = new Thread(new Producer(good),"生产线程");
Thread consumerThread = new Thread(new Consumer(good),"消费者线程");
//启动生产者线程
produceThread.start();
Thread.sleep(1000);
//启动消费者线程
consumerThread.start();
}
}
//Goods{goodName='红旗拖拉机一辆', count=1}
//Goods{goodName='红旗拖拉机一辆', count=0}
么现在换一种方式,将生产者线程开启和消费者线程开启的代码换个位置在再测试下。此时问题产生了,生产者还没生产商品消费者就消费了导致数量不正确,此时就需要我们的wait()和notify()方法帮忙
public class Test{
public static void main(String[] args) throws InterruptedException {
Goods good = new Goods();
Thread produceThread = new Thread(new Producer(good),"生产线程");
Thread consumerThread = new Thread(new Consumer(good),"消费者线程");
//启动消费者线程
consumerThread.start();
//启动生产者线程
produceThread.start();
Thread.sleep(1000);
}
}
//Goods{goodName='null', count=-1}
//Goods{goodName='红旗拖拉机一辆', count=0}
修改如上代码:
class Goods{
//商品名
private String goodName;
//商品库存
private int count;
//生产方法
public synchronized void set(String goodName) throws InterruptedException {
if(this.count > 0){
System.out.println("还有库存");
wait();
}
this.goodName = goodName;
this.count = count + 1;
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{" + "goodName='" + goodName + '\'' + ", count=" + count + '}';
}
}
//生产者
class Producer implements Runnable{
private Goods good;
public Producer(Goods good) {
this.good = good;
}
@Override
public void run() {
try {
this.good.set("红旗拖拉机一辆");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费者
class Consumer implements Runnable{
private Goods good;
public Consumer(Goods good) {
this.good = good;
}
@Override
public void run() {
try {
this.good.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
Goods good = new Goods();
Thread produceThread = new Thread(new Producer(good),"生产线程");
Thread consumerThread = new Thread(new Consumer(good),"消费者线程");
//启动消费者线程
consumerThread.start();
//启动生产者线程
produceThread.start();
Thread.sleep(1000);
}
}
//商品已售罄,请等待
//Goods{goodName='红旗拖拉机一辆', count=1}
//Goods{goodName='红旗拖拉机一辆', count=0}
2.2.多生产以及多消费
以上只有一个生产者生产一次商品和一个消费者只消费一次就结束了,现在能否改变一下,多个生产者和多个消费者呢?这样的话我们该怎么改造代码呢?首先notify方法目前是只能唤醒一个线程,如果有多个生产者线程和多个消费者线程的话,这个notify方法唤醒的线程如果是消费者的话应该没有问题,但是如果是唤醒的也是生产者的线程那么程序就会变成假死状态了,这个时候显然这个notify方法不行,我们上一节讲过了有一个notifyAll()唤醒当前对象的所有线程。这个时候就可以使用该方法了
修改多生产:
//生产方法
public synchronized void set(String goodName) throws InterruptedException {
if (this.count > 0) {
System.out.println("还有库存");
wait();
}
this.goodName = goodName;
this.count = count + 1;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
System.out.println(toString());
//生产完商品之后通知消费者可以消费了
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 {
//此时如果还没有生产好的商品,就先等生产者生产
if(this.count == 0){
System.out.println("商品已售罄,请等待");
wait();
}
//每次消费一个产品
this.count = this.count - 1;
sleep(1000);
System.out.println(Thread.currentThread().getName());
System.out.println("消费" + toString());
//消费完商品可以通知生产者继续生产了
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();
}
}
}
3.一个面试题
>>>请解释sleep()与wait()的区别:
- sleep()是
Thread
类中定义的方法,到了一定的时间后该线程自动唤醒,不会释放对象锁
- wait()是
Object
类中定义的方法,要想唤醒必须使用notify()、notifyAll()方法才能唤醒,会释放对象锁