java并发编程——生产者消费者模式之阻塞队列版

小咪咪 2022-12-09 11:56 261阅读 0赞

=====文章目录=====

  • 生产者消费者模式
    • 传统版
    • 阻塞队列版
      • 为什么用?有什么好处?
      • BlockingQueue的核心方法
      • 架构介绍
      • 种类分析
      • 代码
  • 参考资料

生产者消费者模式

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的

传统版

用ReentrantLock实现。

  1. package mytest;
  2. /** * 共享资源类 */
  3. class ShareData {
  4. private int num = 0;
  5. private Lock lock = new ReentrantLock();
  6. private Condition condition = lock.newCondition();
  7. public void increment() throws Exception {
  8. lock.lock();
  9. try {
  10. //判断
  11. while (num != 0) {
  12. //等待 不生产
  13. condition.await();
  14. }
  15. //干活
  16. num++;
  17. System.out.println(Thread.currentThread().getName() + "\t" + num);
  18. //通知唤醒
  19. condition.signalAll();
  20. } finally {
  21. lock.unlock();
  22. }
  23. }
  24. public void deIncrement() throws Exception {
  25. lock.lock();
  26. try {
  27. //判断
  28. while (num == 0) {
  29. //等待 不生产
  30. condition.await();
  31. }
  32. //干活
  33. num--;
  34. System.out.println(Thread.currentThread().getName() + "\t" + num);
  35. //通知唤醒
  36. condition.signalAll();
  37. } finally {
  38. lock.unlock();
  39. }
  40. }
  41. }
  42. /** * Description * 一个初始值为0的变量 两个线程交替操作 一个加1 一个减1来5轮 * * @author veliger@163.com * @version 1.0 * @date 2019-04-13 14:01 **/
  43. public class ProdConsumerTraditionDemo {
  44. public static void main(String[] args) {
  45. ShareData shareData = new ShareData();
  46. new Thread(() -> {
  47. for (int i = 1; i <= 5; i++) {
  48. try {
  49. shareData.increment();
  50. } catch (Exception e) {
  51. e.printStackTrace();
  52. }
  53. }
  54. }, "AA").start();
  55. new Thread(() -> {
  56. for (int i = 1; i <= 5; i++) {
  57. try {
  58. shareData.deIncrement();
  59. } catch (Exception e) {
  60. e.printStackTrace();
  61. }
  62. }
  63. }, "BB").start();
  64. }
  65. }

阻塞队列版

阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如图所示:
在这里插入图片描述
当阻塞队列是空时,从队列中获取元素的操作将会被阻塞.
当阻塞队列是满时,往队列中添加元素的操作将会被阻塞.
同样
试图往已满的阻塞队列中添加新圆度的线程同样也会被阻塞,知道其他线程从队列中移除一个或者多个元素或者全清空队列后使队列重新变得空闲起来并后续新增.

为什么用?有什么好处?

在多线程领域:所谓阻塞,在某些情况下会挂起线程(即线程阻塞),一旦条件满足,被挂起的线程优惠被自动唤醒

为什么需要使用BlockingQueue

好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为BlockingQueue都一手给你包办好了

在concurrent包 发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度.

BlockingQueue的核心方法

BlockingQueue插入元素的方法用四种,移除的也有四种,检查的有两种。
在这里插入图片描述
不同类型方法的动作。


























方法类型 动作
抛出异常 当阻塞队列满时,再往队列里面add插入元素会抛IllegalStateException: Queue full
当阻塞队列空时,再往队列Remove元素时候回抛出NoSuchElementException
特殊值 插入方法,成功返回true 失败返回false
移除方法,成功返回元素,队列里面没有就返回null
一直阻塞 当阻塞队列满时,生产者继续往队列里面put元素,队列会一直阻塞直到put数据or响应中断退出
当阻塞队列空时,消费者试图从队列take元素,队列会一直阻塞消费者线程直到队列可用.
超时退出 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出

架构介绍

在这里插入图片描述

种类分析

ArrayBlockingQueue: 由数组结构组成的有界阻塞队列.
LinkedBlockingDeque: 由链表结构组成的有界(但大小默认值Integer>MAX_VALUE)阻塞队列.
PriorityBlockingQueue:支持优先级排序的无界阻塞队列.
DelayQueue: 使用优先级队列实现的延迟无界阻塞队列.
SynchronousQueue:不存储元素的阻塞队列,也即是单个元素的队列.
LinkedTransferQueue:由链表结构组成的无界阻塞队列.
LinkedBlockingDeque:由了解结构组成的双向阻塞队列.

代码

  1. class MyResource {
  2. /** * 默认开启 进行生产消费的交互 */
  3. private volatile boolean flag = true;
  4. /** * 默认值是0 */
  5. private AtomicInteger atomicInteger = new AtomicInteger();
  6. private BlockingQueue<String> blockingQueue = null;
  7. public MyResource(BlockingQueue<String> blockingQueue) {
  8. this.blockingQueue = blockingQueue;
  9. System.out.println(blockingQueue.getClass().getName());
  10. }
  11. public void myProd() throws Exception {
  12. String data = null;
  13. boolean returnValue;
  14. while (flag) {
  15. data = atomicInteger.incrementAndGet() + "";
  16. returnValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
  17. if (returnValue) {
  18. System.out.println(Thread.currentThread().getName() + "\t 插入队列数据" + data + "成功");
  19. } else {
  20. System.out.println(Thread.currentThread().getName() + "\t 插入队列数据" + data + "失败");
  21. }
  22. TimeUnit.SECONDS.sleep(1);
  23. }
  24. System.out.println(Thread.currentThread().getName() + "\t 停止 表示 flag" + flag);
  25. }
  26. public void myConsumer() throws Exception {
  27. String result = null;
  28. while (flag) {
  29. result = blockingQueue.poll(2L, TimeUnit.SECONDS);
  30. if(null==result||"".equalsIgnoreCase(result)){
  31. flag=false;
  32. System.out.println(Thread.currentThread().getName()+"\t"+"超过2m没有取到 消费退出");
  33. System.out.println();
  34. System.out.println();
  35. return;
  36. }
  37. System.out.println(Thread.currentThread().getName() + "消费队列" + result + "成功");
  38. }
  39. }
  40. public void stop() throws Exception{
  41. flag=false;
  42. }
  43. }
  44. /** * Description * volatile/CAS/atomicInteger/BlockQueue/线程交互/原子引用 * * @author veliger@163.com * @version 1.0 * @date 2019-04-13 14:02 **/
  45. public class ProdConsumerBlockQueueDemo {
  46. public static void main(String[] args) throws Exception {
  47. MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
  48. new Thread(()->{
  49. System.out.println(Thread.currentThread().getName()+"\t生产线程启动");
  50. try {
  51. myResource.myProd();
  52. } catch (Exception e) {
  53. e.printStackTrace();
  54. }
  55. },"Prod").start();
  56. new Thread(()->{
  57. System.out.println(Thread.currentThread().getName()+"\t消费线程启动");
  58. try {
  59. myResource.myConsumer();
  60. } catch (Exception e) {
  61. e.printStackTrace();
  62. }
  63. },"consumer").start();
  64. try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
  65. System.out.println();
  66. System.out.println();
  67. System.out.println();
  68. System.out.println("时间到,停止活动");
  69. myResource.stop();
  70. }
  71. }

参考资料

生产者消费者模型—详解及代码实现 c++版

发表评论

表情:
评论列表 (有 0 条评论,261人围观)

还没有评论,来说两句吧...

相关阅读

    相关 阻塞队列实现生产者消费者模式

    生产者消费者模式是并发、多线程编程中经典的 [设计模式][Link 1],生产者和消费者通过分离的执行工作解耦,简化了开发模式,生产者和消费者可以以不同的速度生产和消费数据。这