『并发包入坑指北』之阻塞队列(上)

简介: 较长一段时间以来我都发现不少开发者对 jdk 中的 J.U.C(java.util.concurrent)也就是 Java 并发包的使用甚少,更别谈对它的理解了;但这却也是我们进阶的必备关卡。其中的内容主要包含以下几个部分:根据定义自己实现一个并发工具。JDK 的标准实现。实践案例。所以本次重点讨论 ArrayBlockingQueue。

自己实现


在自己实现之前先搞清楚阻塞队列的几个特点:


  • 基本队列特性:先进先出。


  • 写入队列空间不可用时会阻塞。


  • 获取队列数据时当队列为空时将阻塞。


实现队列的方式多种,总的来说就是数组和链表;其实我们只需要搞清楚其中一个即可,不同的特性主要表现为数组和链表的区别。


这里的 ArrayBlockingQueue 看名字很明显是由数组实现。


我们先根据它这三个特性尝试自己实现试试。


初始化队列


我这里自定义了一个类:ArrayQueue,它的构造函数如下:


public ArrayQueue(int size) {
        items = new Object[size];
    }


很明显这里的 items 就是存放数据的数组;在初始化时需要根据大小创建数组。



写入队列


写入队列比较简单,只需要依次把数据存放到这个数组中即可,如下图:



但还是有几个需要注意的点:


  • 队列满的时候,写入的线程需要被阻塞。


  • 写入过队列的数量大于队列大小时需要从第一个下标开始写。


先看第一个队列满的时候,写入的线程需要被阻塞,先来考虑下如何才能使一个线程被阻塞,看起来的表象线程卡住啥事也做不了。


有几种方案可以实现这个效果:


  • Thread.sleep(timeout)线程休眠。


  • object.wait() 让线程进入 waiting 状态。


当然还有一些 join、LockSupport.part 等不在本次的讨论范围。


阻塞队列还有一个非常重要的特性是:当队列空间可用时(取出队列),写入线程需要被唤醒让数据可以写入进去。


所以很明显Thread.sleep(timeout)不合适,它在到达超时时间之后便会继续运行;达不到空间可用时才唤醒继续运行这个特点。


其实这样的一个特点很容易让我们想到 Java 的等待通知机制来实现线程间通信;更多线程见通信的方案可以参考这里:深入理解线程通信


所以我这里的做法是,一旦队列满时就将写入线程调用 object.wait() 进入 waiting 状态,直到空间可用时再进行唤醒。


/**
     * 队列满时的阻塞锁
     */
    private Object full = new Object();
    /**
     * 队列空时的阻塞锁
     */
    private Object empty = new Object();



所以这里声明了两个对象用于队列满、空情况下的互相通知作用。


在写入数据成功后需要使用 empty.notify(),这样的目的是当获取队列为空时,一旦写入数据成功就可以把消费队列的线程唤醒。


这里的 wait 和 notify 操作都需要对各自的对象使用 synchronized 方法块,这是因为 wait 和 notify 都需要获取到各自的锁。


消费队列


上文也提到了:当队列为空时,获取队列的线程需要被阻塞,直到队列中有数据时才被唤醒。



代码和写入的非常类似,也很好理解;只是这里的等待、唤醒恰好是相反的,通过下面这张图可以很好理解:



总的来说就是:


  • 写入队列满时会阻塞直到获取线程消费了队列数据后唤醒写入线程


  • 消费队列空时会阻塞直到写入线程写入了队列数据后唤醒消费线程


相关文章
|
6月前
|
Java 程序员
惊呆了!LinkedList的这些队列功能,99%的程序员都没用过!
【6月更文挑战第18天】`LinkedList`不仅是Java集合中的列表实现,还可作队列(`peek()`,`add()`,`remove()`)和双端队列(`Deque`,`addFirst()`,`addLast()`,`peekFirst()`,`peekLast()`),甚至栈(`push()`,`pop()`,`peek()`)。常被低估,其实它具备从两端操作数据的强大能力,适合多种数据结构需求。
41 6
|
7月前
|
监控 Java 测试技术
面试准备不充分,被Java守护线程干懵了,面试官主打一个东西没用但你得会
面试准备不充分,被Java守护线程干懵了,面试官主打一个东西没用但你得会
64 1
|
7月前
|
存储 缓存 Oracle
Java线程池,白话文vs八股文,原来是这么回事!
一、线程池原理 1、白话文篇 1.1、正式员工(corePoolSize) 正式员工:这些是公司最稳定和最可靠的长期员工,他们一直在工作,不会被解雇或者辞职。他们负责处理公司的核心业务,比如生产、销售、财务等。在Java线程池中,正式员工对应于核心线程(corePoolSize),这些线程会一直存在于线程池中。他们负责执行线程池中的任务,如果没有任务,他们会等待新的任务到来。 1.2、所有员工(maximumPoolSize) 所有员工:这些是公司所有的员工,包括正式员工和外包员工。他们共同组成了公司的团队,协作完成公司的各种业务。在Java线程池中,所有员工对应于所有线程(maxim
|
7月前
|
安全
带你手搓阻塞队列——自定义实现
带你手搓阻塞队列——自定义实现
88 0
|
消息中间件 JavaScript 小程序
新来个阿里 P7,仅花 2 小时,撸出一个多线程永动任务,看完直接跪了,真牛逼!
新来个阿里 P7,仅花 2 小时,撸出一个多线程永动任务,看完直接跪了,真牛逼!
|
安全 Java API
『并发包入坑指北』之阻塞队列(下)
较长一段时间以来我都发现不少开发者对 jdk 中的 J.U.C(java.util.concurrent)也就是 Java 并发包的使用甚少,更别谈对它的理解了;但这却也是我们进阶的必备关卡。其中的内容主要包含以下几个部分: 根据定义自己实现一个并发工具。 JDK 的标准实现。 实践案例。 所以本次重点讨论 ArrayBlockingQueue。
|
Java API C++
朴实的聊聊很多人会误解/不懂的Java并发中断机制
朴实的聊聊很多人会误解/不懂的Java并发中断机制
朴实的聊聊很多人会误解/不懂的Java并发中断机制
|
安全 Java API
『并发包入坑指北』之向大佬汇报任务
在面试过程中聊到并发相关的内容时,不少面试官都喜欢问这类问题: 当 N 个线程同时完成某项任务时,如何知道他们都已经执行完毕了。
|
存储 缓存 监控
如果你是 JDK 设计者,如何设计线程池?我跟面试官大战了三十个回合(下)
如果你是 JDK 设计者,如何设计线程池?我跟面试官大战了三十个回合(下)
如果你是 JDK 设计者,如何设计线程池?我跟面试官大战了三十个回合(下)
|
存储 消息中间件 算法
一文详解「队列」,手撸队列的3种方法!
一文详解「队列」,手撸队列的3种方法!
164 0
一文详解「队列」,手撸队列的3种方法!