生产者消费者模型(wait/notify/notifyAll)

生产者和消费者之间的阻塞队列

生产者消费者模型在实际生活中很多运用。对我们自己来说就是一个消费者,比如我们需要买奥利奥饼干,我们会去超时买,并不是直接从厂商那里买,而厂商把奥利奥生产结束后会送往各个超市进行售卖,超市就是我们消费者和生产者之间媒介。这个超市在操作系统定义为阻塞队列。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从
阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

在了解生产者消费者模型之前需要先了解几个方法~~

wait()方法

    public final void wait() throws InterruptedException 

wait()使线程停止运行,会释放对象锁。

  • wait()方法必须用于同步代码块和同步方法,而且必须是内建锁(synchronized),因为需要锁住的对象。如果调用wait()时没有合适的锁,会抛异常。
  • wait()方法会使当前线程调用该方法后进行等待,并且将该线程置入锁对象的等待队列中,直到接到通知或被中断而已。如果没有接到通知或者中断,会一直等。
  • wait()方法执行后当前线程释放锁,其他线程可以竞争该锁。
package CODE.多线程;

public class Wait1
{
    public static void main(String[] args) throws InterruptedException {
        Object ob=new Object();
        synchronized (ob)
        {
            System.out.println("等待开始...");
            ob.wait();
            System.out.println("等待结束...");
        }
        System.out.println("main方法结束...");
    }
}

只会打印出“等待开始…”,因为没有唤醒等待,该进程就一直等待…,不会运行后面语句。

wait方法从运行态到阻塞态

wait (long time):如果到了预计时间还没被唤醒,将继续执行后续代码

wait( )之后的线程继续执行有2种方法:

  • 调用该对象的notify( )方法唤醒等待线程
  • 线程等待时调用interrupt( )中断该线程

1.首先用notify()唤醒线程:

notify()

    public final native void notify();

  • notify( )方法也必须在同步方法或者同步代码块中调用,用来唤醒等待在该对象上的线程。如果有多个线程等待,则任意挑一个线程唤醒.
  • notify( )方法执行后,唤醒线程不会立即释放对象锁,要等待唤醒线程全部执行才会释放锁。
import static java.lang.Thread.sleep;

//notify()
class waitnotifyT
{
    synchronized public void  waitMethod() {
        System.out.println("等待开始...");
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("等待结束...");
    }
    synchronized public void  notifyMethod() {
        System.out.println("唤醒开始...");
       notify();
        System.out.println("唤醒结束...");
    }
}


class Mthread implements  Runnable
{
    private waitnotifyT waitnotifyT1;
    private int flag=1;
    public Mthread(waitnotifyT waitnotifyT1,int flag) {
        this.waitnotifyT1 = waitnotifyT1;
        this.flag=flag;
    }

    @Override
    public void run()
    {
        if(flag==1)
        {
            waitnotifyT1.waitMethod();
        }
        else
            waitnotifyT1.notifyMethod();
    }
}
public class Wait1
{
    public static void main(String[] args) throws InterruptedException {
        waitnotifyT wn=new waitnotifyT();
        Mthread  waitThread=new Mthread(wn,1);
        Mthread notifyThread=new Mthread(wn,0);
        new Thread(waitThread).start();
        sleep(1000);
        new Thread(notifyThread).start();
    }
}

在这里插入图片描述
从结果可以看见当执行notify后,唤醒线程不会立即释放锁,而是唤醒线程全部执行完,才会释放锁,执行wait()之后的代码。

2.当线程使用wait导致线程阻塞,调用interrupt会抛一个InterruptedException异常,将线程终止。

class WaitTh implements Runnable
{
    synchronized private void waitM()
    {
        System.out.println("等待开始...");
        try {
            wait();
        } catch (InterruptedException e) {
            System.out.println("等待被中断");        }
    }
    public void run()
    {
        waitM();
    }
}
public class Wait1
{
    public static void main(String[] args)  {
        Thread thread=new Thread(new WaitTh());
        thread.start();
        thread.interrupt();
    }
}

在这里插入图片描述

notify和wait等待和唤醒的是同一个对象

notifyAll()
notifyAll方法可以一次唤醒所有的等待在该对象上的线程。

    public final native void notifyAll();

////notifyAll()

class waitnotifyT
{
    synchronized public void  waitMethod() {
        System.out.println(Thread.currentThread().getName()+":等待开始...");
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":等待结束...");
    }
    synchronized public void  notifyMethod() {
        System.out.println(Thread.currentThread().getName()+":唤醒开始...");
        notifyAll();
        System.out.println(Thread.currentThread().getName()+":唤醒结束...");
    }
}


class Mthread implements  Runnable
{
    private waitnotifyT waitnotifyT1;
    private int flag=1;
    public Mthread(waitnotifyT waitnotifyT1,int flag) {
        this.waitnotifyT1 = waitnotifyT1;
        this.flag=flag;
    }

    @Override
    public void run()
    {
        if(flag==1)
        {
            waitnotifyT1.waitMethod();
        }
        else
            waitnotifyT1.notifyMethod();
    }
}
public class Wait1
{
    public static void main(String[] args) throws InterruptedException {
        waitnotifyT wn=new waitnotifyT();
        Mthread  waitThread=new Mthread(wn,1);
        Mthread notifyThread=new Mthread(wn,0);
        new Thread(waitThread,"等待线程1").start();
        new Thread(waitThread,"等待线程2").start();
        new Thread(waitThread,"等待线程3").start();
        new Thread(waitThread,"等待线程4").start();
        new Thread(waitThread,"等待线程5").start();
        new Thread(notifyThread,"唤醒线程").start();

    }
}

在这里插入图片描述

出现线程阻塞的几种情况:
1.调用sleep( )方法,主动放弃占有的cpu,不会释放对象锁
2.调用阻塞式IO方法(read( )、write( )),在该方法前,线程阻塞;
3.线程试图获取某个monitor,但该monitor被其他线程所持有导致阻塞;
4.线程等待某个通知,即调用wait( )方法(join方法是wait方法一层包装,也会出现线程阻塞);释放对象锁;
5.调用线程suspend(),将线程挂起,容易导致死锁,已被废弃。


run()方法运行结束后,会进入销毁阶段,整个线程执行完毕。

同步队列和等待队列:

每一个对象监视器monitor都有2个队列,一个称同步队列,一个称等待队列。

  • 同步队列:因为竞争monitor(对象锁)失败导致阻塞的线程,这些线程等待cpu再次调度。或者说同步队列存储了将要获得锁的线程。
  • 等待队列:因为调用wait()导致等待的线程,唤醒后进入同步队列竞争锁。唤醒后不会立即竞争锁,而是先进入同步队列,然后等待cpu调度再次竞争锁。

单个生产者消费模型:

一个生产者,一个消费者。对生产者来说,当生产数量大于0时,即还有库存,即先等消费者消费;对消费者来说,如果产品数量小于等于0,需要先den生产者生产。
代码如下:

package CODE.多线程;

////生成者消费者模型

class Goods
{
    private String goodsName;  //商品名称
    private int counts;       //商品数量

    synchronized public void pro(String goodsName)
    {
        //产品数量大于0,等待消费者消费
        if(counts>0)
        {
            try {
                System.out.println("等一会生产,还有库存");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        counts++;
        this.goodsName=goodsName;
        System.out.println(Thread.currentThread().getName()+"生产"+this.goodsName+":"+counts);
        notify(); //生产结束后,唤醒等待的消费者
    }
    synchronized public void Con()
    {
        //产品数量为0,等待生产者生产
        if(counts==0)
        {
            try {
                System.out.println("等一会来买,没有库存");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        counts--;
        System.out.println(Thread.currentThread().getName()+"消费"+this.goodsName+":"+counts);
        notify(); //唤醒等待的生产者
    }

}
class Trade implements Runnable
{
    private Goods goods;
    //生产者生产产品
    private int flag;

    public Trade(Goods goods, int flag) {
        this.goods = goods;
        this.flag = flag;
    }

    @Override
    public void run()
    {
        if(flag==1)
        {
            goods.pro("奥利奥饼干");
        }
        else
        {
            goods.Con();
        }
    }
}
public class ProCon {
    public static void main(String[] args)
    {
        Goods goods=new Goods();
        new Thread(new Trade(goods,1),"生产者1").start();
        new Thread(new Trade(goods,0),"消费者1").start();
    }
}

在这里插入图片描述
以上针对的是生产一次,消费一次。但是在实际生活中更多的是生产多次,消费多次。即多生产多消费。

多生产多消费:
多生产:可以多生产的产品数量有一个上限;
多消费:消费者是只要有产品就可以消费。

package CODE.多线程;


////生成者消费者模型

import java.util.ArrayList;
import java.util.List;

class MoreGoods
{
    private String goodsName;  //商品名称
    private int counts;       //商品数量

    synchronized public void pro(String goodsName)  {
         //20ms生产一个商品
         try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            counts++;
            this.goodsName=goodsName;
            System.out.println(Thread.currentThread().getName()+"生产"+this.goodsName+":"+counts);
            notifyAll(); //唤醒等待的消费者

    }
    synchronized public void Con()
    {
        //用循环是因为如果一消费者等待一会后,另一个消费者将唯一的产品消费,那么该消费者就需要再重新判断是否有资源消费
            while (counts <= 0) {
                try {
                    System.out.println("等一会来买,没有库存");
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //100ms消费一个
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        counts--;
        System.out.println(Thread.currentThread().getName() + "消费" + this.goodsName + ":" + counts);

    }
    public int getCounts() {
        return counts;
    }
}
class MoreTrade implements Runnable
{
    private MoreGoods goods;
    //生产者生产产品
    private int flag;

    public MoreTrade(MoreGoods goods, int flag) {
        this.goods = goods;
        this.flag = flag;
    }

    @Override
    public void run() {

        if (flag == 1) {
            while (goods.getCounts() < 200) {
                goods.pro("奥利奥饼干");
            }
        } else {
            while (true)
                goods.Con();

        }
    }
}
public class MorePro {
    public static void main(String[] args) {
        MoreGoods goods = new MoreGoods();
        List<Thread> list=new ArrayList<>();  //数组存的是生产消费线程,因为生产消费线程需要同时启动
        //5个生产者
        for(int i=0;i<5;i++)
        {
            Thread thread=new Thread(new MoreTrade(goods,1),"生产者"+i);
            list.add(thread);
        }
        //3个消费者 
        for(int i=0;i<3;i++)
        {
            Thread thread=new Thread(new MoreTrade(goods,0),"消费者"+i);
            list.add(thread); 
        }
        for(Thread thread:list) {
           thread.start();  //将所有线程都启动
        }
    }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/sophia__yu/article/details/84236491