Java队列Queue详细分析
-
非阻塞队列:
- 无界线程安全:ConcurrentLinkedQueue
- 采用CAS机制:compareAndSwapObject原子操作
-
阻塞队列:
- 有界:ArrayBlockingQueue,设置了大小的LinkedBlockingQueue.通常情况下,LinkedBlockingQueue是有界队列
- 无界:没有设置大小的LinkedBlockingQueue
- 采用锁机制:DelayQueue,PriorityBlockingQueue
- 使用ReetrantLock锁
-
有界队列: 有固定大小的队列
- 设定了固定大小的LinkedBlockingQueue
- 大小为0,只是在生产者和消费者之间做中转使用的SynchronousQueue
-
无界队列: 没有设置固定大小的队列
- 无界队列的特点是可以直接入列,直到溢出. 溢出值为Integer.MAX_VALUE. 现实中不会达到这么大容量,所以通常可以认为是无界的
- 没有设定固定大小的LinkedBlockingQueue
Queue基本概念
- 在线程池中使用阻塞队列LinkedBlockingQueue时,一般情况下需要配置一下队列的大小,设置成有界队列,否则会导致JVM内存被耗尽
- Queue: 通常情况下,一个队列就是一个先入先出FIFO的数据结构
- Queue是与List,Set同一级别,继承自Collection接口
- LinkedList实现了Deque接口
没有实现阻塞接口
- 继承自AbstrsctQueue接口
- 内置两个不阻塞队列 : PriorityQueue和ConcurrentLinkedQueue
- PriorityQueue和ConcurrentLinkedQueue类在Collection框架下增加两个具体集合的实现
-
PriorityQueue:
- 实质上维护了一个有序列表
- 加入到Queue中的元素根据元素实现的java.util.Comparable排序或者传递给构造函数的java.util.Comparator来实现元素定位
-
ConcurrentLinkedQueue:
- 是基于链接节点的线程安全队列
- 并发访问不需要同步.因为ConcurrentLinkedQueue对元素的操作是在队列的尾部添加元素并从头部删除元素,所以不需要知道队列的大小
- ConcurrentLinkedList可以很好地对公共集合进行共享访问
- ConcurrentLinkedList对查询集合大小会很慢,因为需要遍历队列
实现阻塞接口
- 在Java.util.concurrent包中加入了BlockingQueue接口和5个阻塞队列
- BlockingQueue实质上就是带有一点扭曲的FIFO数据接口: 不是立即从队列中添加或者删除元素,会存在线程操作阻塞,直到队列中有空间或者有元素可用
-
BlockingQueue接口的5个阻塞队列:
-
ArrayBlockingQueue: 一个由数组支持的有界队列
- 在构造时需要指定容量,并可以选择是否需要公平性
- 如果公平参数被设置为true, 等待时间最长的线程会优先得到处理
- 就是通过将ReetrantLock设置为true来达到等待时间最长的线程优先操作的公平性
- 公平性需要消耗性能,只有在的确非常需要时才会使用
- ArrayBlockingQueue是基于数组的阻塞循环队列,按照先进先出FIFO原则对元素进行排序
- 实现简单,操作稳定. 添加删除使用同一个锁,性能较低
- 在构造时需要指定容量,并可以选择是否需要公平性
-
LinkedBlockingQueue: 一个由链表支持的可选有界队列
- 使用LinkedBlockingQueue通常情况下需要选择指定最大容量
- 在不指定LinkedBlockingQueue容量时容量为Integer.MAX_VALUE, 可以认为是无界的,但是在put时是会存在阻塞情况的
- LinkedBlockingQueue是基于链表的队列,按照先进先出FIFO排序元素
- LinkedBlockingQueue的添加和删除操作两把锁是分开的
-
PriorityBlockingQueue: 一个由优先级堆支持的无界优先级队列
- 一个带优先级队列,而不是先进先出队列,元素按优先级顺序被移除 ,PriorityBlockingQueue队列没有上限
- PriorityBlockingQueue是对PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,类似ArrayList, 所以在优先级阻塞队列上使用put时是不会发生阻塞的
- 虽然PriorityBlockingQueue在逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作时会导致OutOfMemoryError异常
- 如果队列为空,那么取元素的操作take就会发生阻塞,所以检索操作take是受阻的
- 添加到PriorityBlockingQueue中的元素需要具有比较能力
-
DelayQueue: 一个由优先级堆支持的基于时间的调度队列
- 基于PriorityQueue实现的基于时间的调度队列
- 是一个存放Delayed元素的无界阻塞队列,只有在延迟期满时才能从中取出元素
- DelayQueue元素的头部是延迟期满后保存时间最长的Delayed元素
- 如果延迟都还没有期满,则DelayQueue没有头部,并且poll操作将会返回null
- 当一个元素的getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或者等于0的值时,则延迟期满 ,poll就可以移除这个元素
- 此队列不允许使用null元素
- 通常用于清除缓存中超时的缓存数据
-
SynchronousQueue: 一个由阻塞队列BlockingQueue接口的简单聚集机制
- 内部容量为0, 适用于元素数量少的场景. 特别适合作为交换数据使用
-
SynchronousQueue内部使用队列实现公平性的调度,使用栈实现非公平性的调度. 使用CAS实现锁逻辑
-
ArrayBlockingQueue: 一个由数组支持的有界队列
- remove, element, offer, poll, peek都是属于Queue接口
- 阻塞队列的操作可以根据响应方式分为3类:
- add, remove和element操作在为一个已满的队列增加元素或者从空队列取出元素时抛出异常
- 在多线程的程序中,队列在任何情况下都可能是已满或者空状态,可以使用offer,poll,peek方法,这些方法在无法完成任务时,只是给出一个出错提示而不会抛出异常
- 注意: 因为在多线程环境中,使用poll,peek操作出错时会返回null, 所以向队列中插入null值是不合法的
- 阻塞操作有put和take, put方法在队列满时阻塞 ,take方法在队列空时阻塞
add
- 增加一个元素
- 如果队列已满,则抛出IllegalStateException异常
remove
- 移除并返回队列头部的元素
- 如果队列为空,则抛出NoSuchElementException异常
element
- 返回队列头部的元素
- 如果队列为空,则抛出一个NoSuchElementException异常
offer
- 添加一个元素并返回true
- 如果队列已满,则返回false
poll
- 移除并返回队列头部的元素
- 如果队列为空,则返回null
peek
- 返回队列头部的元素
- 如果队列为空,则返回null
put
- 添加一个元素
- 如果已满,则发生阻塞
take
- 移除并返回队列头部的元素
- 如果队列为空,则发生阻塞
Java集合框架位于java.util包中
Collections提供了对集合进行排序,遍历等多种算法实现,比如**sort(), reverse(), shuffle()**等
- Collection
- List : 采用线性列表的存储方式,存储的顺序与添加的顺序相同
- ArrayList
- Vector
- LinkedList
- Set : 不保证元素的顺序,不允许重复的元素
- TreeSet
- HashSet
- Map : 采用键值对的存储方式,键值不允许重复
- TreeMap
- HashMap
- HashTable