热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

大话阻塞队列(阻塞唤醒),生产者、消费者的实现

队列先进先出的一种数据结构,那什么是阻塞队列呢?从名字可以看出阻塞队列其实也就是队列的一种特殊情况。从上面这张图我们会发现这样的规律:

队列先进先出的一种数据结构,那什么是阻塞队列呢?

从名字可以看出阻塞队列其实也就是队列的一种特殊情况。
在这里插入图片描述
从上面这张图我们会发现这样的规律:

(1)当阻塞队列为空时,从队列中获取元素的操作将会被阻塞,就好比餐馆休息区没人了,此时不能接纳新的顾客了。换句话,肚子为空的时候也没东西吃。

(2)当阻塞队列满了,往队列添加元素的操作将会被阻塞,好比餐馆的休息区也挤满了,后来的顾客只能走了。

从上面的概念我们类比到线程中去,我们会发现,在某些时候线程可能不能不阻塞,因为CPU内核就那么几个,阻塞现状更加说明了资源的利用率高,换句话来说,阻塞其实是一个好事。

阻塞队列应用最广泛的是生产者和消费者模式。在没有阻塞队列前是这样子的,
版本1:synchronized ,wait,notifyAll 配合实现线程阻塞、唤醒

class XiaoFeiSyn{private int number=0;public synchronized void product() throws Exception {try {while (number != 0) {//阻塞this.wait();}number++;System.out.println(Thread.currentThread().getName() + " 生产消息...");this.notifyAll();}catch (Exception e) {e.printStackTrace();}}public synchronized void consume () throws Exception {try {while (number == 0) {//阻塞this.wait();}number--;System.out.println(Thread.currentThread().getName() + " 消费消息...");this.notifyAll();}catch (Exception e) {e.printStackTrace();}}
}public static void test6(){XiaoFeiSyn fei&#61;new XiaoFeiSyn();new Thread(()->{try {for (int i &#61; 0; i < 10; i&#43;&#43;) {fei.product();}}catch (Exception e) {e.printStackTrace();}finally {}}, "AA").start();new Thread(()->{try {for (int i &#61; 0; i < 10; i&#43;&#43;) {fei.consume();}}catch (Exception e) {e.printStackTrace();}finally {}}, "BB").start();}//运行结果&#xff1a;AA 生产消息...BB 消费消息...AA 生产消息...BB 消费消息...AA 生产消息...BB 消费消息...AA 生产消息...BB 消费消息...AA 生产消息...BB 消费消息...

分析可知&#xff1a;两个线程可交替执行

版本2&#xff1a;Lock ,Condition ,await,signalAll 配合

class XiaoFei{private int number&#61;0;private Lock lock&#61;new ReentrantLock();private Condition condition&#61;lock.newCondition();public void product() throws Exception{try{lock.lock();while (number!&#61;0){//阻塞condition.await();}number&#43;&#43;;System.out.println(Thread.currentThread().getName()&#43;" 生产消息...");condition.signalAll();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}//XiaoFei fei&#61;new XiaoFei();new Thread(()->{try {for (int i &#61; 0; i < 10; i&#43;&#43;) {fei.product();}}catch (Exception e) {e.printStackTrace();}finally {}}, "AA").start();new Thread(()->{try {for (int i &#61; 0; i < 10; i&#43;&#43;) {fei.consume();}}catch (Exception e) {e.printStackTrace();}finally {}}, "BB").start();

分析可知&#xff1a;两个线程可交替执行

版本3&#xff1a;基于阻塞队列实现&#xff0c;BlockingQueue

class QueueProAndConsume{public volatile boolean flag&#61;true;BlockingQueue<String> bq&#61;null;AtomicInteger atmoic&#61;new AtomicInteger();public QueueProAndConsume(BlockingQueue<String> bq) {this.bq &#61; bq;System.out.println("实现类&#xff1a;"&#43;bq.getClass().getName());}public void product() throws Exception{String data&#61;null;while (flag){//生产data&#61;atmoic.getAndIncrement()&#43;"";boolean pro&#61;bq.offer(data,2L,TimeUnit.SECONDS);if(pro){System.out.println(Thread.currentThread().getName()&#43;" 生产成功。。"&#43;data);}else {System.out.println(Thread.currentThread().getName()&#43;" 生产失败。。"&#43;data);}}}public void consume() throws Exception{String data&#61;null;while (flag){//消费String consume&#61;bq.poll(2L,TimeUnit.SECONDS);if(consume&#61;&#61;null){flag&#61;false;System.out.println(Thread.currentThread().getName()&#43;" 超过2秒没有取到消息-");}TimeUnit.SECONDS.sleep(1);System.out.println(Thread.currentThread().getName()&#43;" 消费&#xff1a;"&#43;consume);}}
QueueProAndConsume queue&#61;new QueueProAndConsume(new SynchronousQueue<>());new Thread(()->{try {queue.product();}catch (Exception e) {e.printStackTrace();}},"product-").start();new Thread(()->{try {queue.consume();}catch (Exception e) {e.printStackTrace();}},"consume-").start();TimeUnit.SECONDS.sleep(10);queue.flag&#61;false;//运行结果&#xff1a;实现类&#xff1a;java.util.concurrent.SynchronousQueueproduct- 生产成功。。0consume- 消费&#xff1a;0product- 生产成功。。1consume- 消费&#xff1a;1product- 生产成功。。2consume- 消费&#xff1a;2product- 生产成功。。3consume- 消费&#xff1a;3product- 生产失败。。4

分析可知&#xff1a;BlockingQueue 没加任何锁、wait 唤醒。也实现了线程的唤醒和阻塞
1、使用了资源类 2、while 防止虚假唤醒 3、volatile boolean flag&#61;true; 线程间可见

为啥呢 &#xff1f;
在这里插入图片描述
通过上面代码认识了阻塞队列&#xff0c;接着进一步熟悉阻塞API
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
SynchronousQueue 队列&#xff0c;无容量、每次put之前必须等待上一个消费掉&#xff0c;

一种阻塞队列&#xff0c;其中每个插入操作必须等待另一个线程的对应移除操作 &#xff0c;反之亦然。同步队列没有任何内部容量&#xff0c;甚至连一个队列的容量都没有。不能在同步队列上进行 peek&#xff0c;因为仅在试图要移除元素时&#xff0c;该元素才存在&#xff1b;除非另一个线程试图移除某个元素&#xff0c;否则也不能&#xff08;使用任何方法&#xff09;插入元素&#xff1b;也不能迭代队列&#xff0c;因为其中没有元素可用于迭代。队列的头 是尝试添加到队列中的首个已排队插入线程的元素&#xff1b;如果没有这样的已排队线程&#xff0c;则没有可用于移除的元素并且 poll() 将会返回 null。对于其他 Collection 方法&#xff08;例如 contains&#xff09;&#xff0c;SynchronousQueue 作为一个空 collection。此队列不允许 null 元素。 public static void blockTest(){BlockingQueue<String> blockingDeque&#61;new SynchronousQueue<>();new Thread(()->{try {for (int i &#61; 0; i < 10; i&#43;&#43;) {blockingDeque.put("a");System.out.println(Thread.currentThread().getName()&#43;"put...."&#43;i);}}catch (Exception e) {e.printStackTrace();}finally {}}, "t1").start();new Thread(()->{try {for (int i &#61; 0; i < 10; i&#43;&#43;) {blockingDeque.take();TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName()&#43;"get...."&#43;i);}}catch (Exception e) {e.printStackTrace();}finally {}}, "t2").start();}//运行结果&#xff1a;t1 -put....0t2 -get....0t1 -put....1t2 -get....1t1 -put....2

public static void test4() throws Exception{BlockingQueue<String> blockingDeque&#61;new ArrayBlockingQueue<>(3);blockingDeque.add("a");blockingDeque.remove("a");blockingDeque.offer("a");blockingDeque.offer("a",2,TimeUnit.SECONDS);blockingDeque.poll();//&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;blockingDeque.put("a");blockingDeque.take();}

当然阻塞队列除了可用于生产者—消费者&#xff0c;还可用于线程池&#xff0c;消息中间件。。


推荐阅读
  • 本文将详细探讨 Java 中提供的不可变集合(如 `Collections.unmodifiableXXX`)和同步集合(如 `Collections.synchronizedXXX`)的实现原理及使用方法,帮助开发者更好地理解和应用这些工具。 ... [详细]
  • 本文探讨了如何通过一系列技术手段提升Spring Boot项目的并发处理能力,解决生产环境中因慢请求导致的系统性能下降问题。 ... [详细]
  • 深入解析SpringMVC核心组件:DispatcherServlet的工作原理
    本文详细探讨了SpringMVC的核心组件——DispatcherServlet的运作机制,旨在帮助有一定Java和Spring基础的开发人员理解HTTP请求是如何被映射到Controller并执行的。文章将解答以下问题:1. HTTP请求如何映射到Controller;2. Controller是如何被执行的。 ... [详细]
  • 本题要求在一组数中反复取出两个数相加,并将结果放回数组中,最终求出最小的总加法代价。这是一个经典的哈夫曼编码问题,利用贪心算法可以有效地解决。 ... [详细]
  • 深入理解Java多线程并发处理:基础与实践
    本文探讨了Java中的多线程并发处理机制,从基本概念到实际应用,帮助读者全面理解并掌握多线程编程技巧。通过实例解析和理论阐述,确保初学者也能轻松入门。 ... [详细]
  • java文本编辑器,java文本编辑器设计思路
    java文本编辑器,java文本编辑器设计思路 ... [详细]
  • 本文探讨了如何使用pg-promise库在PostgreSQL中高效地批量插入多条记录,包括通过事务和单一查询两种方法。 ... [详细]
  • 掌握Mosek矩阵运算,轻松应对优化挑战
    本篇文章继续深入探讨Mosek学习笔记系列,特别是矩阵运算部分,这对于优化问题的解决至关重要。通过本文,您将了解到如何高效地使用Mosek进行矩阵初始化、线性代数运算及约束域的设定。 ... [详细]
  • 本文详细介绍了如何在Kendo UI for jQuery的数据管理组件中,将行标题字段呈现为锚点(即可点击链接),帮助开发人员更高效地实现这一功能。通过具体的代码示例和解释,即使是新手也能轻松掌握。 ... [详细]
  • 软件工程课堂测试2
    要做一个简单的保存网页界面,首先用jsp写出保存界面,本次界面比较简单,首先是三个提示语,后面是三个输入框,然 ... [详细]
  • 本文详细探讨了Java中的ClassLoader类加载器的工作原理,包括其如何将class文件加载至JVM中,以及JVM启动时的动态加载策略。文章还介绍了JVM内置的三种类加载器及其工作方式,并解释了类加载器的继承关系和双亲委托机制。 ... [详细]
  • 本文详细解析了Java中throw和throws的关键区别,同时涵盖了JDK的定义、Java虚拟机的关键约定、Java的跨平台性、自动垃圾回收机制、源文件结构、包的概念及作用等多个核心知识点,旨在帮助学生更好地准备Java期末考试。 ... [详细]
  • 在许多地理位置选择类的应用程序中,侧边栏是常见的用户界面元素,用于通过选择特定的字母快速定位和选择地点。本文将详细介绍如何在Android应用中创建一个具有波浪效果的自定义侧边栏,以提升用户体验。 ... [详细]
  • 利用jstack进行死锁检测与线程堆栈分析
    本文介绍了如何使用jstack工具进行Java应用中的死锁检测及高CPU使用率线程的堆栈分析,帮助开发者快速定位并解决性能瓶颈。 ... [详细]
  • 在Java应用程序开发过程中,FTP协议被广泛用于文件的上传和下载操作。本文通过Jakarta Commons Net库中的FTPClient类,详细介绍如何实现文件的上传和下载功能。 ... [详细]
author-avatar
殇猿
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有