多线程设计模式——十七、生产者消费者设计模式

生产者和消费者也是一个非常经典的多线程模式,我们在实际开发中应用非常广泛的思 想理念。在生产消费模式中:通常由两类线程,即若干个生产者的线程和若干个消费者的线 程。生产者线程负责提交用户请求,消费者线程则负责具体处理生产者提交的任务,在生产 者和消费者之间通过共享内存缓存区进行通信。

在这里插入图片描述
具体代码逻辑实现思路:
在这里插入图片描述
代码0:Main,外面使用的设计模式的类

package com.bjsxt.chapter17.demo03;

import java.util.concurrent.*;

/**
 * @author whl
 * @date 2021/2/24
 */
public class Main {
    public static void main(String[] args) {
        // 关键:生产者消费者共享一个数据缓冲区
        BlockingQueue<Data> dataQueue=new LinkedBlockingQueue<>();
        // 创建生产业务对象
        Producer p1=new Producer(dataQueue);
        Producer p2=new Producer(dataQueue);
        // 创建消费业务对象
        Consumer c1=new Consumer(dataQueue);
        Consumer c2=new Consumer(dataQueue);
        Consumer c3=new Consumer(dataQueue);
        // 使用线程池里头的线程并启动
        ExecutorService threadPool= Executors.newCachedThreadPool();
        threadPool.execute(p1);
        threadPool.execute(p2);
        threadPool.execute(c1);
        threadPool.execute(c2);
        threadPool.execute(c3);
        // 三秒后关闭生产操作
        try{
            TimeUnit.SECONDS.sleep(30);
        }catch (Exception e){
            e.printStackTrace();
        }
        p2.stop();
        p1.stop();
    }
}

代码1:Data

package com.bjsxt.chapter17.demo03;

/**
 * @author whl
 * @date 2021/2/24
 */
public class Data {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Data(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

代码2:Producer

package com.bjsxt.chapter17.demo03;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author whl
 * @date 2021/2/24
 */

/**
 * 生产业务
 */
public class Producer implements Runnable{
    // 关键:共享数据的缓冲区
    private BlockingQueue<Data> dataQueue;
    // 重点:停止生产的标记变量,
    private volatile boolean isRunning=true;
    // 线程池放进任务对象,每个对象要共享资源count,代表生产第几个数据
    private static AtomicInteger count=new AtomicInteger(0);
    // 生产者使用缓冲数据
    public Producer(BlockingQueue<Data> dataQueue){
        this.dataQueue=dataQueue;
    }

    @Override
    public void run() {
        while (isRunning){
            try{
                int i=count.incrementAndGet();
                Data data=new Data(i,"data-"+i);
                // 模拟生产时间:使用休眠
                TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000));
                if(!dataQueue.offer(data, ThreadLocalRandom.current().nextInt(1000), TimeUnit.MILLISECONDS)){
                    System.out.println("producer thread: "+Thread.currentThread().getName()+" submit data wrong,data id :"+data.getId()+"(produce)");
                    // 重新提交 等操作
                }else{
                    System.out.println("producer thread: "+Thread.currentThread().getName()+" submit one data,data id :"+data.getId()+"(produce)");                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    // 将标记变量设置为fasle
    public void stop(){
        this.isRunning=false;
    }
}

代码3:Consumer

package com.bjsxt.chapter17.demo03;

import java.sql.Time;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
 * @author whl
 * @date 2021/2/24
 */
public class Consumer implements Runnable {
    // 关键:共享数据的缓冲区引用变量
    private BlockingQueue<Data> dataQueue;
    // 设置引用变量
    public Consumer(BlockingQueue<Data> dataQueue) {
        this.dataQueue = dataQueue;
    }

    @Override
    public void run() {
        while (true){
            try{
                Data data = dataQueue.take();
                // 模拟消费数据的时间
                TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000));
                System.out.println("consumer thread : "+Thread.currentThread().getName()+" consume data, data id is "+data.getId());
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

运行结果
2个生产者线程30s生产了128个数据,3个消费者线程30s消费完毕.生产者线程60s后未执行会被vm回收,消费者线程进入了阻塞状态,等待数据生产消费。所以程序hang着。
在这里插入图片描述
在这里插入图片描述
jstack查看这6个线程运行状态:
15:53:40
main线程处于普通的BLOCKED状态
在这里插入图片描述
pool-1-thread-1,2,3,4,5线程由于模拟生产数据、消费数据,而陷入普通的阻塞状态
在这里插入图片描述
2021-02-24 15:53:44
main 线程由于执行sleep处阻塞状态
在这里插入图片描述
pool-Thread-1,2正在生产数据,处于普通的阻塞状态;
pool-Thread-3正在消费数据,处于普通的阻塞状态;
pool-Thread-4,5由于dataQueue.take()失败,没有数据要取数据陷入parking阻塞状态,等待被生产数据的线程唤醒。
在这里插入图片描述
2021-02-24 15:54:01
main线程仍然阻塞,30s还是挺长的hh
在这里插入图片描述
pool-Thread-1,2正在生产数据、3,4,5正在消费数据
在这里插入图片描述
2021-02-24 15:54:07
main线程执行完毕了!!30s过去了。那么pool-Thread-1,2这两个生产者线程的isRunning=false,会停止运行。
在这里插入图片描述
pool-Thread-1,2仍然存在!虚拟机栈帧中是看不懂的东西,线程池会销毁60s未执行的县城对象。
pool-Thread-3,4,5进入取不到数据而阻塞的wait队列阻塞状态。
在这里插入图片描述
2021-02-24 15:56:35
main线程已经寿终正寝。
在这里插入图片描述
54 07到现在56 35有两分钟了,按照语气java虚拟机栈中应该没有pool-Thread-1,2,事实与结果一致。
3,4,5这三个消费者县城等待被唤醒。
在这里插入图片描述
总结:
生产者业务对象和消费者业务对象有一个引用变量指向公共的数据缓冲区,在此次的实现中选择的是无界阻塞队列类型保存数据。数据也是一个对象。因此至少是需要设计三个类:生产者业务类,消费者业类,数据类。线程执行单元的两种方式,使用实现Runnabel接口方式。
除此之外,需要设计一个Main类,创建无界阻塞队列对象、生产者对象、消费者对象、线程池、调用execute启动5个线程。执行30s后关闭消费者两个线程。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值