Java同步数据结构之Collection-Queue
概述
接下来开始学习java.util.concurrent包中一些Collection集合的子类,关于Map的一些子类将在这些子类完成之后再开始学习。下图是Java并发包中关于Collection接口的一些实现的关系类图,当然为了简化复杂度,我没有就所有的父子关系都列出来,而是仅仅就一些特殊的关系画了出来,例如ConcurrentSkipListSet 不仅继承了AbstractSet,它还实现了NavigableSet、Cloneable, java.io.Serializable接口。Note: 绿色表示接口,蓝色表示实例类,黄色表示抽象类。
上面的类结构图可以分为三个大部分,分别是Set、List、Queue,双向Queue即Deque只是Queue的一种特殊实现。上图左上角是Set相关的类图,右上角是List相关的类图(仅仅只有一个CopyOnWriteArrayList),右下角是双向队列Deque的相关类图,中下部分都是队列Queue的类图。与对列Queue相关的类占据了JUC并发包的大部分内容。
java.util.Queue
作为Collection的直接子接口,Queue的作用当然也是用于存取数据,除了拥有Collection的基本操作,Queue提供了一系列额外的用于插入、提取和元素检测操作。这些方法都以两种形式存在:①如果操作失败,则抛出异常;② 返回一个特殊值(null或false,取决于具体操作)。后一种形式的插入操作专门设计用于对容量有限制的队列的操作。
以下六个接口方法就是Queue接口定义的所有方法:
操作失败抛异常 | 操作失败返回特殊值 | |
---|---|---|
插入 | boolean add(E e) | boolean offer(E e) |
获取并且移除 | E remove() | E poll() |
获取但不移除 | E element() | E peek() |
插入方法add(e)和offer(e) --- add方法尽管有个boolean返回值,但是其实只有成功才会返回true,失败会直接抛出异常;offer方法才被设计成失败返回false,成功返回true。
获取并移除方法remove和poll --- 都是返回并删除队列的头节点元素,不同的是在队列为空时,remove抛出异常,poll返回null。
获取不移除方法element和peek --- 都是返回但是不删除队列的头节点元素,不同的是在队列为空时,element抛出异常,peek返回null。
关于插入null值,队列实现通常不允许插入空元素,虽然有些实现,如LinkedList,不禁止插入null。即使在允许null的实现中,也不应该将null插入到队列中,因为 poll和peek 方法将null用作特殊的返回值,以指示队列不包含元素。
关于hashCode和equals方法,队列实现通常不定义基于元素版本的equals和hashCode方法,而是从Object类继承基础版本,因为对于具有相同元素但元素在集合中的排序顺序的情况不总是友好的。
关于元素排序,队列通常(但并非一定)以 FIFO(先进先出)的方式排序各个元素。例如优先级队列PriorityQueues,它根据提供的比较器或元素的自然顺序对元素进行排序;LIFO的队列(例如堆栈)按后进先出的方式排队元素。无论使用哪种排序方式,队列的头都是调用 remove() 或 poll() 所移除的元素。在 FIFO 队列中,所有的新元素都插入队列的末尾。其他种类的队列可能使用不同的元素放置规则。每个Queue的实现必须指定其元素排序方式。
关于阻塞方法,Queue接口中并没有定义这类接口,它们在Queue子接口BlockingQueue接口中定义。
Java.util.AbstractQueue
通过上面的类图可以发现,几乎Queue的所有实现(除了ConcurrentLinkedDeque)都继承了AbstractQueue抽象类,通过查看其源码可以发现,AbstractQueue抽象类其实就是利用了Queue定义的offer、poll、peek方法分别重新实现了add、remove、element方法,但是AbstractQueue的这种重写实现并没有改变add、remove、element方法定义的最终返回结果,在队列满时add方法照样抛出异常,在队列为空时,remove和element方法照样抛出异常。
AbstractQueue类还新增了两个方法,分别是基于poll的clear方法,以及基于add方法的addAll方法。AbstractQueue并没有声明任何抽象方法。
BlockingQueue
该接口继承Queue接口,就是JUC并发包队列的基础接口,它定义了一系列阻塞方法,以支持在获取元素时等待队列变为非空,并在存储元素时等待队列的空间可用。BlockingQueue接口在Queue的基础上将Queue存在两种形式的方法扩展到拥有四种形式,即对于不能立即满足但是在将来的某个时候可能会满足的操作的不同处理方式:1.抛出异常。2.返回一个特殊值(null或false,取决于操作)。3.线程无限期地阻塞当前线程,直到操作成功或被打断。4.等待给定的最大时间限制,直到操作成功或超时或被打断。总结这些方法如下:
操作失败抛异常 | 操作失败返回特殊值 | 条件不满足阻塞 | 条件不满足等待给定的时间 | |
---|---|---|---|---|
插入 | boolean add(E e) | boolean offer(E e) | void put(E e) throws InterruptedException |
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException |
获取并且移除 | boolean remove(Object o) | E poll() | E take() throws InterruptedException |
E poll(long timeout, TimeUnit unit) throws InterruptedException |
获取但不移除 | E element() | E peek() | 不可用 | 不可用 |
表格的前两种类型和Queue接口定义的接口方法一致,但是除了绿色单元格中的方法是直接继承自Queue,其它三个方法其实都被BlockingQueue重新声明了,但是没有改变这些方法的涵义。后面两种类型的方法是BlockingQueue新增的对阻塞的支持方法。
BlockingQueue不接受插入null对象。当尝试add,put或offer一个null时将抛出NullPointerException。null被作为poll/peek方法操作失败之后的返回值。
BlockingQueue新增了一个int remainingCapacity()方法,用于返回当前队列剩余可用的容量,因为BlockingQueue可能是有容量限制的。没有容量限制的BlockingQueue该方法总是返回Integer.MAX_VALUE。
BlockingQueue还新增了两个drainTo方法,用于从当前队列中移除全部或者指定数量的元素到参数指定的集合中。
BlockingQueue实现是线程安全的。所有队列方法都使用内部锁或其他并发控制形式以到达它们的原子效果。但是,除非在实现中特殊说明,否则批量集合操作addAll、containsAll、retainAll和removeAll不一定以原子方式执行。
内存一致性:
线程在将对象放入 BlockingQueue队列之前的操作 happen-before 另一个线程从BlockingQueue获取或者移除该元素之后的操作。
Java.util.Deque
一种支持从两端插入和移除元素的线性集合。deque是“double ended queue(双端队列)”的缩写,虽然大多数的Dqueu实现都是无界的,但事实上该接口也支持有界的双端队列实现。该接口定义了从双端队列两端访问元素的方法,这样方法同Queue接口一样都是用于对元素的插入、提取和检测操作,每种方法也都存在两种形式:①操作失败抛异常;②返回特殊值(null或false,取决于具体操作)。后一种形式的插入操作专门设计用于对容量有限制的队列的操作。这些方法有如下12个:
第一个元素(即头部) | 最后一个元素(即尾部) | |||
失败抛异常 | 失败返回特殊值 | 失败抛异常 | 失败返回特殊值 | |
插入 | addFirst(e) | offerFirst(e) | addLast(e) | offerLast(e) |
移除 | removeFirst() | pollFirst() | removeLast() | pollLast() |
获取但不移除(即检查) | getFirst() | peekFirst() | getLast() | peekLast() |
该接口扩展了Queue接口,将双端队列当着简单队列使用时,它是先进先出的队列,即在双端队列尾部添加新元素,从双端队列头部移除元素,所以Deque其实也具备Queue的特性,凡是Deque中从Queue继承的方法都是按FIFO队列实现,它们定义的接口方法有些是等效的:
操作 | Queue方法 | 等效的Deque方法 |
在对头插入 | add(e) | addLast(e) |
offer(e) | offerLast(e) | |
在队尾移除 | remove() | removeFirst() |
poll() | pollFirst() | |
在对头获取但不移除 | element() | getFirst() |
peek() | peekFirst() |
同样的,双端队列也可以当着后进先出的堆栈使用,即在双端队列的头部进行添加和移除操作,所以Deque其实也具备Stack(堆栈)的特性,它们定义的接口方法也有些是等效的:
操作 | 堆栈方法 | 等效的Deque方法 |
在对头插入 | push(e) | addFirst(e) |
在对头移除 | pop() | removeFirst() |
在对头获取但不移除 | peek() | peekFirst() |
无论将双端队列用作普通的先进先出FIFO队列还是后进先出LIFO的堆栈使用,peek()方法的语义都不变,即都是从双端队列的头部获取(但不移除)元素。
此接口还额外新增加了移除内部元素的方法:removeFirstOccurrence(Object o) 和 removeLastOccurrence(Object o),它们分别表示移除双端队列中第一次或最后一次出现的指定元素。
与List接口不同,此接口不支持通过索引访问队列元素。另外,虽然双端队列Deque没有严格要求禁止插入null元素,但是依然不建议这样做,即使某些Deque的实现支持插入null,因为有一些方法会将null作为特殊的返回值来指示双端队列为空。
关于hashCode和equals方法,双端队列实现通常也不定义基于元素版本的equals和hashCode方法,而是从Object类继承基础版本。
BlockingDeque
类似Queue对应的BlockingQueue,Deque也有与其对应的同步阻塞接口BlockingDeque,它是一种附加了支持阻塞操作的双端队列,即获取元素时等待双端队列变为非空,存储元素时等待双端队列中的空间变得可用。类似BlockingQueue,BlockingDeque也在Deque的基础上扩展出了两种形式的方法,即条件不满足时无限期阻塞直到成功和条件不满足时等待给定的超时时长:
第一个元素(即头部) | 最后一个元素(即尾部) | |||
条件不满足时阻塞 | 条件不满足时等待超时 | 条件不满足时阻塞 | 条件不满足时等待超时 | |
插入 | putFirst(e) | offerFirst(e, time, unit) | putLast(e) | offerLast(e, time, unit) |
移除 | takeFirst() | pollFirst(time, unit) | takeLast() | pollLast(time, unit) |
获取但不移除(即检查) | 不适用 | 不适用 | 不适用 | 不适用 |
从列表可见,BlockingDeque不对元素检查(即获取但不移除)做任何更改,像所有 BlockingQueue 一样,BlockingDeque也是线程安全的,但不允许插入null 元素,并且可能有(也可能没有)容量限制。
其实BlockingDeque还继承了BlockingQueue ,只是最上面的类结构图没有画出来,所以BlockingDeque也是一种BlockingQueue,它可以直接被当成FIFO的BlockingQueue使用。继承自 BlockingQueue 接口的方法精确地等效于下表中描述的 BlockingDeque 方法:
操作 | BlockingQueue 方法 | 等效的 BlockingDeque 方法 |
插入 | add(e) | addLast(e) |
offer(e) | offerLast(e) | |
put(e) | putLast(e) | |
offer(e, time, unit) | offerLast(e, time, unit) | |
移除 | remove() | removeFirst() |
poll() | pollFirst() | |
take() | takeFirst() | |
poll(time, unit) | pollFirst(time, unit) | |
获取但不移除(即检查) | element() | getFirst() |
peek() | peekFirst() |
内存一致性效果:
与其它并发集合一样,将对象放入 BlockingDeque
之前的线程中的操作 happen-before 随后通过另一线程从 BlockingDeque
中访问或移除该元素的操作。
Java同步数据结构之Collection-Queue的更多相关文章
- Java同步数据结构之ConcurrentSkipListMap/ConcurrentSkipListSet
引言 上一篇Java同步数据结构之Map概述及ConcurrentSkipListMap原理已经将ConcurrentSkipListMap的原理大致搞清楚了,它是一种有序的能够实现高效插入,删除,更 ...
- Java同步数据结构之LinkedTransferQueue
前言 LinkedTransferQueue是Java并发包中最强大的基于链表的无界FIFO阻塞传输队列.从JDK7开始出现,Doug Lea说LinkedTransferQueue是Concurre ...
- Java同步数据结构之SynchronousQueue
前言 严格来说SynchronousQueue并不是像它的名字那样是一种Queue,它更像是一个数据接力的交汇点,还记得在介绍Exchanger的时候提到过Exchanger可以看作是Synchron ...
- Java同步数据结构之PriorityBlockingQueue
前言 接下来继续BlockingQueue的另一个实现,优先级阻塞队列PriorityBlockingQueue.PriorityBlockingQueue是一个无限容量的阻塞队列,由于容量是无限的所 ...
- Java同步数据结构之ArrayBlockingQueue
引言 作为BlockingQueue最常见的实现类之一,ArrayBlockingQueue是通过数组实现的FIFO先进先出有界阻塞队列,它的大小在实例被初始化的时候就被固定了,不能更改.该类支持一个 ...
- Java同步数据结构之DelayQueue/DelayedWorkQueue
前言 前面介绍了优先级队列PriorityBlockingQueue,顺带也说了一下PriorityQueue,两者的实现方式是一模一样的,都是采用基于数组的平衡二叉堆实现,不论入队的顺序怎么样,ta ...
- Java同步数据结构之CopyOnWriteArrayList/CopyOnWriteArraySet
前言 前面介绍完了队列(包括双端队列),今天探讨以下Java并发包中一个List的并发数据结构实现CopyOnWriteArrayList,顾名思义CopyOnWriteArrayList也是一种基于 ...
- Java同步数据结构之LinkedBlockingQueue
前言 比起ArrayBlockingQueue,LinkedBlockingQueue应该是最被大家常用的阻塞队列,LinkedBlockingQueue是基于链表的一种可选容量的阻塞队列,也就是说, ...
- Java同步数据结构之ConcurrentLinkedDeque
前言 由于LinkedBlockingDeque作为双端队列的实现,采用了单锁的保守策略使其不利于多线程并发情况下的使用,故ConcurrentLinkedDeque应运而生,它是一种基于链表的无界的 ...
随机推荐
- 第三章 Lambda表达式
第三章 Lambda表达式 3.1 函数式编程思想概述 在数学中,函数就是有输入量.输出量的一套计算方案,也就是“拿什么东西做什么事情”.相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函 ...
- jumpserver跳板机docker安装小小趟坑
最近日常运维的时候发现每次登陆服务器都要打开终端目录连接对应的服务器,闲暇的时候还好,运维任务很重的时候才发现这样的玩法很傻,浪费时间且一点儿都跟不上潮流,然后打开githup开始搞起来.docker ...
- HTML插入地图
方法/步骤 1.打开“百度地图生成器”的网址:http://api.map.baidu.com/lbsapi/creatmap/index.html 如下图: 2.在“1.定位中心点”中,切换城市,并 ...
- 构建之法第二次作业【使用git和Vs实现四则运算】
[相关信息] Q A GIT地址 git地址 GIT用户名 Lin-000 学号后五位 62501 博客地址 博客地址 作业链接 此次作业链接 1.项目需求 程序接收一个命令行参数 n,然后随机产生 ...
- RNN基础
RNN之所以称为循环神经网路,即一个序列当前的输出与前面的输出也有关.具体的表现形式为网络会对前面的信息进行记忆并应用于当前输出的计算中,即隐藏层之间的节点不再无连接而是有连接的,并且隐藏层的输入不仅 ...
- 开放式最短路径优先OSPF
1.OSPF基本知识 OSPF作为基于链路状态的协议,解决了RIP在收敛慢,路由环路,可扩展性差等问题,还有以下优点: 采用组播方式发布报文,可以减少对其他不运行ospf路由器的影响 ospf直尺无类 ...
- 【HDU5521】Meeting
题目大意:给定一张\(N\)个点的图,构成了\(M\)个团,每个团内的边权均相等,求图上有多少个点满足到\(1\)号节点和\(N\)号节点的最大值最小. 题解: 本题的核心是如何优化连边,考虑对于每一 ...
- python瞎练
需求:有不规则列表 singlelist3 = [ '总计', '每吨人工:', '总人工', 1748.07, '金额'],如果当前元素为字符串且该元素的下一个相邻位置仍为字符串,那么请在该元素后面 ...
- 【bzoj2141】排队 [国家集训队2011]排队(树套树)
题目描述 排排坐,吃果果,生果甜嗦嗦,大家笑呵呵.你一个,我一个,大的分给你,小的留给我,吃完果果唱支歌,大家乐和和. 红星幼儿园的小朋友们排起了长长地队伍,准备吃果果.不过因为小朋友们的身高有所区别 ...
- 洛谷P1006 传纸条【dp】
题目:https://www.luogu.org/problemnew/show/P1006 题意: 给定一个m*n的矩阵,从(1,1)向下或向右走到(m,n)之后向上或向左走回(1,1),要求路径中 ...