多线程:生产者和消费者(等待唤醒机制)代码实现

生产者和消费者模式概述【应用】

  • 概述

    1.生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们     对多线程编程的理解更加深刻

    2.所谓生产者消费者问题,实际上主要是包含了两类线程:

    一类是生产者线程用于生产数据

    一类是消费者线程用于消费数据

    3. 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

    生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

    消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

  • Object类的等待和唤醒方法

    方法名 说明
    void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
    void notify() 唤醒正在等待对象监视器的单个线程
    void notifyAll() 唤醒正在等待对象监视器的所有线程
  •  生产者和消费者,也叫做等待唤醒机制,它是一种十分经典的多线程协作的模式。
  •  线程的执行,它是有随机性的,等待唤醒机制,这个机制,就要打破随机的规则,它会让两个线程轮流执行,你一次我一次,你一次我一次,这就是在等待唤醒机制中,多线程的运行结果。

多线程:生产者和消费者(等待唤醒机制)代码实现_第1张图片

  • 其中的一条线程,我们把它叫做生产者,负责生产数据
  • 另一条线程,叫做消费者,负责消费数据
  • 还需要有第三者桌子,因为线程的执行是具有随机性的,还需要有一个东西去控制线程的执行。
  • 如果桌子上有吃的,消费者负责吃;如果桌子上没有吃的,生产者负责做。
  • 有两种情况,消费者等待和生产者等待。吃货在等:wait;厨师要去唤醒:notify

多线程:生产者和消费者(等待唤醒机制)代码实现_第2张图片

 多线程:生产者和消费者(等待唤醒机制)代码实现_第3张图片

多线程:生产者和消费者(等待唤醒机制)代码实现_第4张图片

多线程:生产者和消费者(等待唤醒机制)代码实现_第5张图片

 等待唤醒机制:消费者和生产者代码实现

package com.gch.d11_wait_and_notify;

/**
   作用:控制生产者和消费者的执行
 */
public class Desk {
    // 是否有面条  0:没有面条  1:有面条
    // boolean:true false(只有两个值,只能控制两条线程的执行)
    public static int foodFlag = 0;

    // 总个数  表示吃货最多可以吃十碗
    public static int count = 10;

    // 锁对象
    public static final Object lock = new Object();
}
package com.gch.d11_wait_and_notify;

/**
   生产者:表示厨师
 */
public class Producer extends Thread {
    /**
     * 调用父类的有参构造器
     * @param name:线程名
     */
    public Producer(String name){
        super(name);
    }
    /*
        1.循环
        2.同步代码块
        3.判断共享数据是否已经到了末尾(到了末尾)
        4.判断共享数据是否已经到了末尾(没有到末尾,执行核心逻辑)
     */
    @Override
    public void run() {
        // 1.循环
        while(true){
            // 2.同步代码块
            synchronized(Desk.lock){
                // 3.判断共享数据是否已经到了末尾(到了末尾)
                if(Desk.count == 0){
                    break;
                }else{
                    // 4.判断共享数据是否已经到了末尾(没有到末尾,执行核心逻辑)
                    // 判断桌子上是否有食物
                    if(Desk.foodFlag == 1){
                        try {
                            // 如果有,就等待
                            Desk.lock.wait(); // 让当前线程跟锁进行绑定起来
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }else{
                        // 如果没有,就制作食物
                        System.out.println("厨师做了一碗面条!");
                        // 修改桌子上的食物状态
                        Desk.foodFlag = 1;
                        // 叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}
package com.gch.d11_wait_and_notify;

/**
   消费者:表示吃货
 */
public class Consumer extends Thread {
    /**
     * 调用父类的有参构造器
     * @param name:线程名
     */
    public Consumer(String name){
        super(name);
    }
    @Override
    public void run() {
       /*
           1.循环
           2.同步代码块
           3.判断共享数据是否到了末尾(到了末尾)
           4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
        */
        // 1.循环
        while(true){
            // 2.同步代码块
            synchronized(Desk.lock){
                // 3.判断共享数据是否已经到了末尾(到了末尾)
                if(Desk.count == 0){
                    break; // 循环一旦停止,线程就要结束了
                }else{  // 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
                    // 先判断桌子上是否有面条
                    if(Desk.foodFlag == 0){
                        try {
                            // 如果没有,就等待
                            // 必须用锁对象去调用wait方法和notifyAll()方法
                            //                        Desk.lock.notifyAll(); // 唤醒跟这把锁绑定的所有线程
                            Desk.lock.wait(); // 让当前线程跟锁进行绑定
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }else{    // 如果有,就开吃
                        // 把吃的总数 - 1
                        Desk.count--;
                        // 如果有,就开吃
                        System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗!");
                        // 吃完之后,唤醒厨师继续做
                        Desk.lock.notifyAll(); // 表示现在要去唤醒,绑定在这把锁上的所有线程
                        // 修改桌子的状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}
package com.gch.d11_wait_and_notify;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            需求:完成生产者和消费者(等待唤醒机制)的代码
                 实现线程轮流交替执行的效果
         */

        // 1.创建线程的对象
        Thread p = new Producer("厨师");
        Thread c = new Consumer("吃货");

        // 2.开启线程
        p.start();
        c.start();
    }
}

多线程:生产者和消费者(等待唤醒机制)代码实现_第6张图片

 二. 阻塞队列实现等待唤醒机制

  • 阻塞队列:连接生产者与消费者之间的管道。
  • 阻塞队列实现了4个接口
  • 多线程:生产者和消费者(等待唤醒机制)代码实现_第7张图片
  • 阻塞队列由于实现了Collection接口,所以阻塞队列就是一个单列集合。
  • 阻塞队列可以利用迭代器或者增强for循环来遍历。
  • 生产者线程和消费者线程必须使用同一个阻塞队列!
  • 有界表示是有长度的界限。因此在创建ArrayBlockingQueue对象的时候,必须要去只当队列的最大长度。
  • 无界指的是没有长度的界限。
  • 常见BlockingQueue(阻塞队列):

    ArrayBlockingQueue: 底层是数组,有界

    LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值

  • BlockingQueue的核心方法:

    put(anObject): 将参数放入队列,如果放不进去会阻塞

    take(): 取出第一个数据,取不到会阻塞

  • 多线程:生产者和消费者(等待唤醒机制)代码实现_第8张图片

package com.gch.d12_wait_and_notify;

import java.util.concurrent.ArrayBlockingQueue;

/**
   生产者:表示厨师
 */
public class Producer extends Thread {
    public ArrayBlockingQueue queue;
    /**
     * 有参构造器
     * @param name:线程名
     * @param queue:阻塞队列
     */
    public Producer(String name, ArrayBlockingQueue queue) {
        super(name);
        this.queue = queue;
    }

    @Override
    public void run() {
        while(true){
            try {
                // 1.不断的把面条放到阻塞队列当中
                queue.put("面条"); // put方法底层源码就已经使用了Lock锁
                System.out.println(getName() + "放了一碗面条!");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
package com.gch.d12_wait_and_notify;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * 消费者:表示吃货
 */
public class Consumer extends Thread {
    public ArrayBlockingQueue queue;
    /**
     * 有参构造器
     * @param name:线程名
     * @param queue:阻塞队列
     */
    public Consumer(String name, ArrayBlockingQueue queue) {
        super(name);
        this.queue = queue;
    }
    @Override
    public void run() {
        while(true){
            // 1.不断的从阻塞队列中获取面条
            String food = null;
            try {
                food = queue.take(); // take方法的底层也是有Lock锁的
                System.out.println(food);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
package com.gch.d12_wait_and_notify;

import java.util.concurrent.ArrayBlockingQueue;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
            细节:
                 生产者和消费者必须使用同一个阻塞队列
         */

        // 1.创建阻塞队列的对象
        // ArrayBlockingQueue是有界阻塞队列,在创建ArrayBlockingQueue对象的时候必须指定它的上限
        ArrayBlockingQueue queue = new ArrayBlockingQueue<>(1);

        // 2.创建线程的对象,并把阻塞队列传递过去
        Thread p = new Producer("厨师",queue);
        Thread c = new Consumer("吃货",queue);

        // 3.开启线程
        p.start();
        c.start();
    }
}

多线程:生产者和消费者(等待唤醒机制)代码实现_第9张图片

 

 

 

你可能感兴趣的:(Java,servlet,java)