JAVA中---生产者与消费者模型

1.Wait()—痴汉方法
wait()就是使线程停止运行,会释放对象锁。

  1. wait()方法会使当前线程调用该方法后进行等待,并且将该线程置入锁对象的等待队列中,直到接到通知或被中断为止。
  2. wait()方法只能在同步方法或同步代码块中调用,如果调用wait()时没有适当的锁,会抛出异常。
  3. wait()方法执行后,当前线程释放锁,其他线程可以竞争该锁。
    例:
package www.java.test;

class MyThread implements Runnable{
    private Object object = new Object();
    @Override
    public void run() {
        synchronized (object){
            System.out.println("wait方法开始...");
            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wait方法结束...");
        }
    }
}

public class Test{
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread thread = new Thread(mt);
        thread.start();
    }
}

我们可以运行这个程序,会发现它一直都没有停下来,在死等,如果没有调用notify()唤醒,它就不会停下来,但还有一种让它停下来的方法就是被中断。
例:

package www.java.test;

class MyThread implements Runnable{
    private Object object = new Object();
    @Override
    public void run() {
        synchronized (object){
            System.out.println("wait方法开始...");
            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wait方法结束...");
        }
    }
}

public class Test{
    public static void main(String[] args) throws InterruptedException {
        MyThread mt = new MyThread();
        Thread thread = new Thread(mt);
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}

在这里插入图片描述
我们发现wait()方法通过中断也停止了。

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

  1. 调用该对象的notify()方法唤醒等待线程
  2. 线程等待时调用interrupt()中断该进程
wait(long time):如果到了预计时间还未被唤醒,线程将继续执行。

例:

package www.java.test;

class MyThread implements Runnable{
    private Object object = new Object();
    @Override
    public void run() {
        synchronized (object){
            System.out.println("wait方法开始...");
            try {
                object.wait(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wait方法结束...");
        }
    }
}

public class Test{
    public static void main(String[] args) throws InterruptedException {
        MyThread mt = new MyThread();
        Thread thread = new Thread(mt);
        thread.start();
    }
}

在这里插入图片描述
2. notify()

  1. notify()方法也必须在同步方法或同步代码块中调用,用来唤醒等待在该对象上的线程。如果有多个线程等待,则任意挑选一个线程唤醒。
  2. notify()方法执行后,唤醒线程不会立刻释放对象锁,要等待唤醒线程全部执行完毕后才释放对象锁。

证明notify()是在等待线程执行完之后才会释放对象锁:

package www.java.test;

class MyThread implements Runnable{
    private Object object;
    private boolean flag;

    public MyThread(Object object, boolean flag) {
        this.object = object;
        this.flag = flag;
    }
    public void waitMethod(){
        synchronized (object){
            System.out.println("wait方法开始..."+Thread.currentThread().getName());
            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wait方法结束..."+Thread.currentThread().getName());
        }
    }
    public void notifyMethod(){
        synchronized (object){
            System.out.println("notify方法开始..."+Thread.currentThread().getName());
            object.notify();
            System.out.println("notify方法结束..."+Thread.currentThread().getName());
        }
    }
    @Override
    public void run() {
        if(flag){
            waitMethod();
        }else{
            notifyMethod();
        }
    }
}

public class Test{
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        MyThread mt1 = new MyThread(object,true);
        MyThread mt2 = new MyThread(object,false);
        Thread waitThread = new Thread(mt1,"等待线程");
        Thread notifyThread = new Thread(mt2,"唤醒线程");
        waitThread.start();
        Thread.sleep(1000);
        notifyThread.start();
    }
}

在这里插入图片描述
证明:多个线程等待时,是任意挑选一个线程唤醒

package www.java.test;

class MyThread implements Runnable{
    private Object object;
    private boolean flag;

    public MyThread(Object object, boolean flag) {
        this.object = object;
        this.flag = flag;
    }
    public void waitMethod(){
        synchronized (object){
            System.out.println("wait方法开始..."+Thread.currentThread().getName());
            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wait方法结束..."+Thread.currentThread().getName());
        }
    }
    public void notifyMethod(){
        synchronized (object){
            System.out.println("notify方法开始..."+Thread.currentThread().getName());
            object.notify();
            System.out.println("notify方法结束..."+Thread.currentThread().getName());
        }
    }
    @Override
    public void run() {
        if(flag){
            waitMethod();
        }else{
            notifyMethod();
        }
    }
}

public class Test{
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        MyThread mt1 = new MyThread(object,true);
        MyThread mt2 = new MyThread(object,false);
        for(int i = 0; i < 10; i++){
            Thread threadi = new Thread(mt1,"等待线程"+i);
            threadi.start();
        }
        Thread notifyThread = new Thread(mt2,"唤醒线程");
        Thread.sleep(1000);
        notifyThread.start();
    }
}

在这里插入图片描述
wait方法结束的只有一个,其他9个都还在运行。
3. notifyAll()
唤醒所有在该对象上等待的线程。
例:

package www.java.test;

class MyThread implements Runnable{
    private Object object;
    private boolean flag;

    public MyThread(Object object, boolean flag) {
        this.object = object;
        this.flag = flag;
    }
    public void waitMethod(){
        synchronized (object){
            System.out.println("wait方法开始..."+Thread.currentThread().getName());
            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("wait方法结束..."+Thread.currentThread().getName());
        }
    }
    public void notifyMethod(){
        synchronized (object){
            System.out.println("notify方法开始..."+Thread.currentThread().getName());
            object.notifyAll();
            System.out.println("notify方法结束..."+Thread.currentThread().getName());
        }
    }
    @Override
    public void run() {
        if(flag){
            waitMethod();
        }else{
            notifyMethod();
        }
    }
}

public class Test{
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        MyThread mt1 = new MyThread(object,true);
        MyThread mt2 = new MyThread(object,false);
        for(int i = 0; i < 10; i++){
            Thread threadi = new Thread(mt1,"等待线程"+i);
            threadi.start();
        }
        Thread notifyThread = new Thread(mt2,"唤醒线程");
        Thread.sleep(1000);
        notifyThread.start();
    }
}

在这里插入图片描述
4. 线程阻塞的几种情况

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

5.monitor的两个队列
同步队列中存放了因为竞争monitor失败导致阻塞的线程,这些线程等待CPU调度再次竞争锁。
等待队列存放因为调用wait()导致线程等待的线程,唤醒后进入同步队列竞争锁。

我们先来看一个简单的生产者消费者模型:
在看消费者模型前,我们先来看看这段代码:

package www.java.test;

class Goods{
    private String goodsName;
    private int count;
    //生产商品方法
    public synchronized void set(String goodsName){
        this.goodsName = goodsName;
        this.count++;
        System.out.println(Thread.currentThread().getName()+"生产"+goodsName+toString());
    }
    //消费商品方法
    public synchronized void get(){
        this.count--;
        System.out.println(Thread.currentThread().getName()+"消费"+goodsName+toString());
    }
    @Override
    public String toString() {
        return "Goods{" +
                "goodsName='" + goodsName + '\'' +
                ", count=" + count +
                '}';
    }
}
//消费者线程
class Consumer implements Runnable{
    private Goods goods;
    public Consumer(Goods goods) {
        this.goods = goods;
    }
    @Override
    public void run() {
        goods.get();
    }
}
//生产者线程
class Producer implements Runnable{
    private Goods goods;
    public Producer(Goods goods) {
        this.goods = goods;
    }
    @Override
    public void run() {
        goods.set("杨树林口红");
    }
}
public class Test{
    public static void main(String[] args) throws InterruptedException {
        Goods goods = new Goods();
        Thread consumerThread = new Thread(new Consumer(goods),"消费者线程");
        Thread producerThread = new Thread(new Producer(goods),"生产者线程");
        consumerThread.start();
        Thread.sleep(1000);
        producerThread.start();
    }
}

在这里插入图片描述
这段代码有什么不对吗?它不应该也是生产者消费者模型吗?
不是的,这个是先调用了消费者线程,可是这时还没有生产,所以这样是错误的。

再来看看下边这段代码,只要加上wait()、notify(),就算是先启动消费者线程也不会出错。

package www.java.test;

class Goods{
    private String goodsName;
    private int count;
    //生产商品方法
    public synchronized void set(String goodsName){
        if(count > 0){
            System.out.println("还有库存,等会再生产");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.goodsName = goodsName;
        this.count++;
        System.out.println(Thread.currentThread().getName()+"生产"+goodsName+toString());
        notify();
    }
    //消费商品方法
    public synchronized void get(){
        if(count == 0){
            System.out.println("目前没有产品,等会再来");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.count--;
        System.out.println(Thread.currentThread().getName()+"消费"+goodsName+toString());
        notify();
    }
    @Override
    public String toString() {
        return "Goods{" +
                "goodsName='" + goodsName + '\'' +
                ", count=" + count +
                '}';
    }
}
//消费者线程
class Consumer implements Runnable{
    private Goods goods;
    public Consumer(Goods goods) {
        this.goods = goods;
    }
    @Override
    public void run() {
        goods.get();
    }
}
//生产者线程
class Producer implements Runnable{
    private Goods goods;
    public Producer(Goods goods) {
        this.goods = goods;
    }
    @Override
    public void run() {
        goods.set("杨树林口红");
    }
}
public class Test{
    public static void main(String[] args) throws InterruptedException {
        Goods goods = new Goods();
        Thread consumerThread = new Thread(new Consumer(goods),"消费者线程");
        Thread producerThread = new Thread(new Producer(goods),"生产者线程");
        producerThread.start();
        Thread.sleep(1000);
        consumerThread.start();
    }
}

在这里插入图片描述

下面来看看多生产者多消费者模型的代码:

package www.java.test;

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

class Goods{
    private String goodsName;
    private int count;
    public int getCount() {
        return count;
    }
    //生产商品方法
    public synchronized void set(String goodsName) throws Exception {
        Thread.sleep(1000);
        this.goodsName = goodsName;
        this.count++;
        System.out.println(Thread.currentThread().getName()+"生产"+goodsName+toString());
        notifyAll();
    }
    //消费商品方法
    public synchronized void get() throws InterruptedException {
        while(count == 0){
            System.out.println("目前没有产品,等会再来");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Thread.sleep(1000);
        this.count--;
        System.out.println(Thread.currentThread().getName()+"消费"+goodsName+toString());
        notifyAll();
    }
    @Override
    public String toString() {
        return "Goods{" +
                "goodsName='" + goodsName + '\'' +
                ", count=" + count +
                '}';
    }
}
//消费者线程
class Consumer implements Runnable {
    private Goods goods;
    public Consumer(Goods goods) {
        this.goods = goods;
    }
    @Override
    public void run() {
        while(true){
            try {
                goods.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//生产者线程
class Producer implements Runnable{
    private Goods goods;
    public Producer(Goods goods) {
        this.goods = goods;
    }
    @Override
    public void run() {
        while(this.goods.getCount() < 10){
            try {
                goods.set("杨树林口红");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
public class Test{
    public static void main(String[] args) throws InterruptedException {
        Goods goods = new Goods();
        //存储多个生产、消费者线程
        List<Thread> list = new ArrayList<>();
        //10个消费者线程
        for(int i = 0; i < 10; i++){
            Thread thread = new Thread(new Consumer(goods),"消费者线程"+i);
            list.add(thread);
        }
        //5个生产者线程
        for(int i = 0; i < 5; i++){
            Thread thread = new Thread(new Producer(goods),"生产者线程"+i);
            list.add(thread);
        }
        for(Thread thread : list){
            thread.start();
        }
    }
}

这个代码是如何对单生产者消费者模型的代码做修改的呢?
在消费产品的方法里if(count == 0)要改为while(count == 0),这是为什么呢?
因为这里有多个生产者线程,如果只判断一个线程的count是否为0,但另外几个线程还有产品,这时是不需要生产的,可以继续消费,所以需要用while来判断,直到所有线程的count都不为0,才需要等待生产者生产。

还有就是要对生产做修改,它不可能一直生产,总要有个度,但是修改生产方法呢还是修改生产者线程里的run()方法呢?
如果我们修改生产方法,让它里边加一个while(count > 10){wait()},当然这样也可以,但我们就会发现,这时是同一个线程在生产产品,直到count大于10,就等待,这显然是有问题的,没有体现出生产者交替的现象,这是因为在run()函数里是while(true){goods.set("杨树林");},当一个线程进入run()之后是出不来的(set()方法是由synchronized所修饰的,一旦一个线程进去就会获得锁,但是死循环,锁不会被释放,所以其他线程无法生产),所以一直只有那一个线程在生产。所以我们需要改的是run()方法里的代码,这样就可以出现多生产者线程交替生产的现象了。

猜你喜欢

转载自blog.csdn.net/huaijiu123/article/details/86308916