文章目录
- 接口定义
- 队列汇总
- 实例
- LinkedList (List, Queue, Deque, 基于链表的无界非阻塞双端队列, 非线程安全)
- ArrayDeque (Queue, Deque, 基于数组的无界非阻塞双端队列, 非线程安全)
- ConcurrentLinkedQueue (Queue, 基于链表的无界非阻塞队列, 无锁CAS线程安全)
- ConcurrentLinkedDeque (Queue, Deque, 基于链表的无界非阻塞双端队列, 无锁CAS线程安全)
- PriorityQueue (Queue, 基于数组存储的堆(二叉树)的无界非阻塞队列, 非线程安全)
- DelayQueue (Queue, BlockingQueue, 基于 PriorityQueue 的无界阻塞队列, 加锁线程安全)
- SynchronousQueue (Queue, BlockingQueue, 无容量的有界阻塞队列, 无锁CAS线程安全)
- ArrayBlockingQueue (Queue, BlockingQueue, 基于数组的有界阻塞队列, 加锁线程安全)
- LinkedBlockingQueue (Queue, BlockingQueue, 基于链表的可选有界阻塞队列, 加锁线程安全)
- PriorityBlockingQueue (Queue, BlockingQueue, 基于数组存储的堆(二叉树)的无界阻塞队列, 加锁线程安全)
- LinkedTransferQueue (Queue, BlockingQueue, TransferQueue, 基于链表的无界阻塞队列, 无锁CAS线程安全)
- LinkedBlockingDeque (Queue, BlockingQueue, Deque, BlockingDeque, 基于链表的可选有界阻塞双端队列, 加锁线程安全)
接口定义
Queue
public interface Queue<E> extends Collection<E>
Queue, 与 List, Set 同级别, 都继承自 Collection, 是一种用于保存元素的容器.
Queue 一端插入元素, 另一端删除元素. 除了 Collection 的操作外, 还提供了额外的 插入/提取/检查
操作, 这3类方法每一种都有两个不同的实现方式, 一种是操作失败时抛出异常, 一种是操作失败时返回一个特殊值(null/false)
队列中的元素通常是 先进先出 (FIFO) 的, 但有例外, 如优先级队列(根据提供的比较器或元素的自然顺序排序), 如后进先出队列(如栈). 在先进先出队列中, 所有新元素会插入到队列的尾部
Queue 接口没有定义并发编程中常见的阻塞队列方法, 这些等待元素出现或空间可用的方法在扩展该接口的 BlockingQueue 接口中定义
抛出异常 | 返回特殊值 | |||
---|---|---|---|---|
插入, 在队列尾部插入元素 | add(e) | 插入成功返回true, 容量不足报 IllegalStateException | offer(e) | 插入成功返回true, 容量不足返回false |
移除, 获取并移除队列头部元素 | remove() | 成功返回元素, 队列为空报 NoSuchElementException | poll() | 成功返回元素, 队列为空返回null |
检查, 获取但不移除队列头部元素 | element() | 成功返回元素, 队列为空报 NoSuchElementException | peek() | 成功返回元素, 队列为空返回null |
队列实现通常不允许插入 null, 尽管某些实现 如 LinkedList 不禁止插入 null. 即使在允许的实现中, 也不应该将 null 插入到队列中, 因为 null 也被 poll 方法用作特殊返回值, 以指示队列不包含任何元素, 这里通过 poll 方法拿到 null 的话, 会有歧义, 不确定是否真的拿到元素还是队列为空
Deque (同时也是 Queue)
public interface Deque<E> extends Queue<E>
支持两端元素插入和移除的线性集合, 和队列一致, 提供了 插入/提取/检查
操作, 按方向分为头操作和尾操作, 按容量限制分为抛异常和返回特殊值
与 List 不同的是, 该接口不支持用索引访问元素
同样, 在 Deque 中不建议插入 null 元素
头操作(first/head) | 尾操作(last/tail) | |||
---|---|---|---|---|
抛出异常 | 返回特殊值 | 抛出异常 | 返回特殊值 | |
插入 | addFirst(e) | offerFirst(e) | addLast(e) | offerLast(e) |
移除 | removeFirst() | pollFirst() | removeLast() | pollLast() |
检查 | getFirst() | peekFirst() | getLast() | peekLast() |
Deque 作为 Queue 使用时(使用 Queue 定义的方法), 效果等同于使用 Deque 的 尾插 头删 头检查 的相关方法
当把双端队列当做栈来使用时(应优先使用Deque而不是Stack), Stack的方法完全等同于Deque的方法
Stack方法 | 等效的Deque方法 |
---|---|
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |
BlockingQueue (同时也是 Queue)
public interface BlockingQueue<E> extends Queue<E>
阻塞队列, 在队列的基础上, 支持了阻塞操作, 即插入操作时, 如果容器中没有足够的空间, 则插入操作将阻塞直到有空间, 移除操作时, 如果容器是空的, 则移除操作将阻塞直到容器中有了元素
抛出异常 | 返回特殊值 | 阻塞 | 超时阻塞 | |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,unit) |
检查 | element() | peek() | 不支持 | 不支持 |
BlockingQueue 不支持插入 null, 当尝试 add/put/offer 一个 null 时, 将抛出 NullPointerException, null 有特殊意义, 表示 poll 失败
BlockingQueue 可以有容量限制, 不指定容量时默认为Integer.MAX, 容量满时无法插入新元素
BlockingQueue 实现主要用于生产者-消费者队列,但还支持 Collection 接口。因此,例如,可以使用 remove(x) 从队列中删除任意元素。但是,此类操作通常不会非常有效地执行,并且仅用于偶尔使用,例如当取消排队的消息时。
在一个线程中, 添加某元素之前的操作 happen-before 在另一个线程中, 访问或移除该元素之后的操作
Java 语言规范的第 17 章定义了内存操作(例如共享变量的读取和写入)的发生前关系。只有当写操作发生在读操作之前,一个线程写的结果才能保证对另一个线程的读可见。 synchronized 和 volatile 构造以及 Thread.start() 和 Thread.join() 方法可以形成happens-before关系。
BlockingDeque (同时也是 Queue, BlockingQueue, Deque)
public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E>
头操作
抛异常 | 返回特殊值 | 阻塞 | 超时阻塞 | |
---|---|---|---|---|
插入 | addFirst(e) | offFirst(e) | putFirst(e) | offerFirst(e,time,unit) |
移除 | removeFirst() | pollFirst() | takeFirst() | pollFirst(time,unit) |
检查 | getFirst() | peekFirst() | 不支持 | 不支持 |
尾操作
抛异常 | 返回特殊值 | 阻塞 | 超时阻塞 | |
---|---|---|---|---|
插入 | addLast(e) | offLast(e) | putLast(e) | offerLast(e,time,unit) |
移除 | removeLast() | pollLast() | takeLast() | pollLast(time,unit) |
检查 | getLast() | peekLast() | 不支持 | 不支持 |
BlockingDeque 作为 BlockingQueue 使用时(BlockingQueue 中定义的方法), 效果等同于使用 BlockingDeque 的 尾插 头删 头检查 的相关方法
TransferQueue (同时也是 Queue, BlockingQueue)
public interface TransferQueue<E> extends BlockingQueue<E>
BlockingQueue 的插入方法的阻塞, 是阻塞到元素被添加到容器中为止. TransferQueue 做了扩展, 支持插入元素阻塞到有消费者消费为止(不仅仅是添加到队列里就完事, 而是有消费者调用了阻塞方法 take() 和 poll(time,unit))
tryTransfer / transfer 等方法, 元素不会存入到队列中, 而是直接交给等待接收元素的消费者
方法 | 描述 | 返回值 |
---|---|---|
tryTransfer(e) | 将元素立即传输给等待的消费者(不加入容器) | 如果存在等待接收元素的消费者(take/timedpoll), 立即传输指定元素, 返回true, 否则false |
transfer(e) | 将元素立即传输给等待的消费者(不加入容器), 在必要时阻塞等待 | 如果存在等待接收元素的消费者(take/timedpoll), 立即传输指定元素, 否则等待直到有消费者接收该元素 |
tryTransfer(e,time,unit) | 将元素立即传输给等待的消费者(不加入容器), 在必要时阻塞等待一定时间 | 如果存在等待接收元素的消费者(take/timedpoll), 立即传输指定元素, 返回true, 否则等待直到有消费者接收该元素, 如果指定的等待时间内有消费者接收元素, 返回true, 否则返回false |
hasWaitingConsumer() | 判断是否有等待的消费者,即调用了take()方法的线程 | true:有, false:无 |
getWaitingConsumerCount() | 获取等待的消费者的数量 |
队列汇总
队列
先进先出的容器, 尾进头出, 提供 插入/取出(并删除)/取出(不删除) 三种类型的方法, 每种提供抛异常和特殊返回值两种模式
双端队列
在队列的基础上, 支持两端操作, 三种类型的方法都扩展了头尾两个方向 first/last, 可自由组合不同方向的操作, 实现如 队列(先进先出)/栈(后进先出) 等效果
非阻塞 队列/双端队列
不提供阻塞方法的普通队列, 容器 空/满 时, 取出和插入方法不会阻塞, 而是报错或返回特殊值
阻塞 队列/双端队列
在普通队列的基础上, 添加了针对容器 空/满 时, 取出和插入会阻塞的方法, 阻塞队列都是线程安全的
有界队列
队列的容量一经初始化即不可再变, 即队列会有被放满的时候
无界队列
队列的容量不限(最大支持到 Integer.MAX), 所以阻塞的插入方法将不会阻塞
Transfer队列
支持生产者将元素不经过容器缓存而直接交给消费者, 比如 LinkedTransferQueue 和 SynchronousQueue, 据说这种队列比普通队列性能高?
实例
LinkedList (List, Queue, Deque, 基于链表的无界非阻塞双端队列, 非线程安全)
// java.util.LinkedList
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList 实现了 List, Deque 接口, 可以当做 List 使用, 也可以当做 Queue 使用, 也可以当做 Deque 使用, 也可以当做 Stack 使用. List 可以加入 null 元素
ArrayDeque (Queue, Deque, 基于数组的无界非阻塞双端队列, 非线程安全)
// java.util.ArrayDeque
public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable, Serializable
Deque 的数组实现, 数组可扩容, 没有容量限制, 禁止 null, 作为 Stack 使用时比 Stack 快, 作为 Queue 使用时比 LinkedList 快 ???
ConcurrentLinkedQueue (Queue, 基于链表的无界非阻塞队列, 无锁CAS线程安全)
// java.util.concurrent.ConcurrentLinkedQueue
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, java.io.Serializable
基于链表的无界非阻塞队列
使用无锁 CAS 保证线程安全
size() 方法需要遍历元素, 而非直接读取属性, 所以可能会不准
无界 / 不允许 null / 线程安全
ConcurrentLinkedDeque (Queue, Deque, 基于链表的无界非阻塞双端队列, 无锁CAS线程安全)
// java.util.concurrent.ConcurrentLinkedDeque
public class ConcurrentLinkedDeque<E> extends AbstractCollection<E> implements Deque<E>, java.io.Serializable
基于链表的无界非阻塞双端队列
使用无锁 CAS 保证线程安全
size() 方法需要遍历元素, 而非直接读取属性, 所以可能会不准
无界 / 不允许 null / 线程安全
PriorityQueue (Queue, 基于数组存储的堆(二叉树)的无界非阻塞队列, 非线程安全)
// java.util.PriorityQueue
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable
核心: 无界非阻塞队列, 容器内的元素会按照自然顺序或指定比较器顺序被按序取出
基于堆的无界优先级队列. 堆, 实则就是一颗二叉树的抽象, 底层是用一个数组来存储数据的
创建优先级队列时, 如果不给定比较器, 默认是小根堆(顶部是最小元素), 从小到大排序.
如果指定了比较器, 则排序使用比较器比较两个元素, 如果没有指定比较器, 则排序时强行将元素转换为 Comparable, 然后使用其 compareTo 方法比较. 隐式要求不传比较器时, 元素需要实现 Comparable 接口, 不然报 ClassCastException
无界 / 不允许 null / 非线程安全
队列的 poll / remove / peek / element 等访问的是队列头部元素
优先级队列是无界的, 默认的初始容量是11, 会自动扩容, 容量小于64时, 2倍扩容(c+c+2), 否则1.5倍扩容(c+c>>1). 创建时可以指定初始容量大小(即数组大小)
@Test
@SneakyThrows
public void test() {
PriorityQueue<String> queue = new PriorityQueue<>();
queue.offer("a");
queue.offer("d");
queue.offer("g");
queue.offer("b");
queue.offer("e");
queue.offer("c");
queue.offer("f");
System.out.println(queue.size());
System.out.println();
// 检查
System.out.println(queue.peek());
System.out.println(queue.size());
System.out.println();
// 移除
System.out.println(queue.poll());
System.out.println(queue.size());
System.out.println();
System.out.println(queue.poll());
System.out.println(queue.size());
System.out.println();
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println();
System.out.println(queue.poll());
}
7
a
7
a
6
b
5
c
d
e
f
g
null
DelayQueue (Queue, BlockingQueue, 基于 PriorityQueue 的无界阻塞队列, 加锁线程安全)
// java.util.concurrent.DelayQueue
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E>
// java.util.concurrent.Delayed 实现了 Comparable
public interface Delayed extends Comparable<Delayed> {
long getDelay(TimeUnit unit);
}
核心: 无界阻塞队列, 容器内的元素按过期的先后排序, 且元素只有过期后才能被按序取出, 支持阻塞等待元素过期
Delayed元素的无界阻塞队列,元素只有在其延迟到期后(过期后)才能被取出
队列中的元素必须实现 Delayed 接口, 同时实现了 Comparable 接口, 元素需实现 compareTo 与 getDelay 方法
队列的头部是已经过期最久的那个元素
当元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回的值小于或等于零时,就会过期
使用一把锁和一个条件来保证线程安全
size方法返回过期(但未移除)和未过期元素的总数
无界 / 不允许 null / 线程安全
DelayQueue 是基于 PriorityQueue 和 ReentrantLock 实现的, PriorityQueue 使用无参构造器创建, 即小根堆方式, head 为最小元素
为什么 DelayQueue 实现 BlockingQueue 的阻塞功能有何意义? 在无未到期元素时, 移除方法可以阻塞等待元素到期
@Test
@SneakyThrows
public void test() {
@Data
class Element implements Delayed {
private long time; // 这里默认用延迟时间的单位是毫秒
private String name;
public Element(TimeUnit unit, long time, String name) {
this.time = System.currentTimeMillis() + (time > 0 ? unit.toMillis(time) : 0);
this.name = name;
}
public long getDelay(TimeUnit unit) {
// 注意: 可以不使用传入的 unit
return time - System.currentTimeMillis();
}
@Override
public int compareTo(Delayed o) {
return (this.time - ((Element) o).getTime() <= 0) ? -1 : 1;
}
@Override
public String toString() {
return "Element{" +
"now=" + new Date() +
", name='" + name + '\'' +
'}';
}
}
DelayQueue<Element> queue = new DelayQueue<>();
queue.add(new Element(TimeUnit.SECONDS, 5, "1"));
queue.add(new Element(TimeUnit.SECONDS, 3, "2"));
queue.add(new Element(TimeUnit.SECONDS, 4, "3"));
queue.add(new Element(TimeUnit.SECONDS, 1, "4"));
queue.add(new Element(TimeUnit.SECONDS, 1, "5"));
queue.add(new Element(TimeUnit.SECONDS, 2, "6"));
int size = queue.size();
for (int i = 0; i < size; i++) {
System.out.println(queue.take());
}
}
Element{now=Thu Jun 09 01:33:19 CST 2022, name='5'}
Element{now=Thu Jun 09 01:33:19 CST 2022, name='4'}
Element{now=Thu Jun 09 01:33:20 CST 2022, name='6'}
Element{now=Thu Jun 09 01:33:21 CST 2022, name='2'}
Element{now=Thu Jun 09 01:33:22 CST 2022, name='3'}
Element{now=Thu Jun 09 01:33:23 CST 2022, name='1'}
// ...
// 在 for 前面加了延迟 2500 毫秒后
Thread.sleep(2500);
int size = queue.size();
for (int i = 0; i < size; i++) {
System.out.println(queue.take());
}
Element{now=Thu Jun 09 01:36:18 CST 2022, name='5'}
Element{now=Thu Jun 09 01:36:18 CST 2022, name='4'}
Element{now=Thu Jun 09 01:36:18 CST 2022, name='6'}
Element{now=Thu Jun 09 01:36:19 CST 2022, name='2'}
Element{now=Thu Jun 09 01:36:20 CST 2022, name='3'}
Element{now=Thu Jun 09 01:36:21 CST 2022, name='1'}
SynchronousQueue (Queue, BlockingQueue, 无容量的有界阻塞队列, 无锁CAS线程安全)
// java.util.concurrent.SynchronousQueue
public class SynchronousQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable
核心: 有界阻塞队列, 可以当做是一个限定容量为0的阻塞队列, 不过其元素直接由生产者传输给消费者, 不经过容器存储
一个阻塞队列,其中每个插入操作都必须等待另一个线程的相应删除操作,同样每个删除操作都必须等待另一个线程的相应插入操作
该队列没有内部容器, 也没有容量, 即该队列内部不会存储元素(可看作是一个容量为0的阻塞队列)
支持 公平/非公平 模式, 公平: 等待线程按序排队(FIFO), 非公平: 等待线程按栈排队(LIFO), 不同于常说的插队式非公平(有可能减少线程内核态和用户态的切换, 所以性能更高)
使用无锁 CAS 保证线程安全
有界 / 不允许 null / 线程安全
@Test
@SneakyThrows
public void test() {
SynchronousQueue<String> queue = new SynchronousQueue<>();
// 即使不是常规的阻塞队列, 但是阻塞队列的基本用法还是要遵守的
// System.out.println(queue.remove()); // 应该抛异常
// System.out.println(queue.poll()); // 应该 null
// System.out.println(queue.poll(1, TimeUnit.SECONDS)); // 应该超时
// System.out.println(queue.take()); // 阻塞
// queue.add("1"); // 应该抛异常, 因为没有容量, 要想成功必须得有等待中的接收者
// queue.offer("1"); // 应该没有效果, 因为当时没有等待中的接收者
// queue.put("1"); // 阻塞, 直到有接收者, 会把主线程卡死
new Thread(() -> {
try {
System.out.println("子线程");
queue.put("1"); // 在子线程中阻塞
System.out.println("子线程");
} catch (Throwable cause) {
cause.printStackTrace();
}
}).start();
// queue.offer("1", 1, TimeUnit.SECONDS);
System.out.println("----");
new Thread(() -> {
try {
System.out.println("子线程2");
System.out.println(queue.take());
System.out.println("子线程2");
} catch (Throwable cause) {
cause.printStackTrace();
}
}).start();
Thread.sleep(2000);
queue.add("2");
}
ArrayBlockingQueue (Queue, BlockingQueue, 基于数组的有界阻塞队列, 加锁线程安全)
// java.util.concurrent.ArrayBlockingQueue
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable
基于数组的有界阻塞队列, 创建时指定容量, 一经创建, 容量将不可更改, 支持对等待的生产者和消费者线程进行公平/非公平排序, 默认非公平
使用一把锁和 notEmpty / notFull 两个条件来保证线程安全, 插入和删除用的是同一把锁
有界 / 不允许 null / 线程安全
LinkedBlockingQueue (Queue, BlockingQueue, 基于链表的可选有界阻塞队列, 加锁线程安全)
// java.util.concurrent.LinkedBlockingQueue
public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable
基于链表的可选有界阻塞队列, 创建时如果指定容量, 则有界且容量不可更改, 如果没有指定, 则是无界
使用两把锁和 notEmpty / notFull 两个条件来保证线程安全, 插入和删除用各自的锁, 所以性能理论上高于 ArrayBlockingQueue
可有界可无界 / 不允许 null / 线程安全
PriorityBlockingQueue (Queue, BlockingQueue, 基于数组存储的堆(二叉树)的无界阻塞队列, 加锁线程安全)
// java.util.concurrent.PriorityBlockingQueue
public class PriorityBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable
基于数组存储的堆(二叉树)的无界阻塞队列, 默认容量是11, 也可指定创建时的容量, 可自动扩容, 支持自然顺序和指定比较器顺序, 自然顺序时需要元素实现 Comparable
基本等同于在 PriorityQueue 的基础上提供了阻塞队列的相关方法支持, 但是是线程安全的
使用一把锁和 notEmpty 条件来保证线程安全(因为无界, 所以没有 notFull 条件)
无界 / 不允许 null / 线程安全
LinkedTransferQueue (Queue, BlockingQueue, TransferQueue, 基于链表的无界阻塞队列, 无锁CAS线程安全)
// java.util.concurrent.LinkedTransferQueue
public class LinkedTransferQueue<E> extends AbstractQueue<E> implements TransferQueue<E>, java.io.Serializable
基于链表的无界阻塞队列, 在阻塞队列的基础上, 扩展了 transfer 相关插入方法, 支持 非阻塞/阻塞 地将元素直接传输给消费者, 而不存储到容器内
使用无锁 CAS 保证线程安全
size() 方法需要遍历元素, 而非直接读取属性, 所以可能会不准
无界 / 不允许 null / 线程安全
LinkedBlockingDeque (Queue, BlockingQueue, Deque, BlockingDeque, 基于链表的可选有界阻塞双端队列, 加锁线程安全)
// java.util.concurrent.LinkedBlockingDeque
public class LinkedBlockingDeque<E> extends AbstractQueue<E> implements BlockingDeque<E>, java.io.Serializable
基于链表的可选有界阻塞双端队列, 创建时如果指定容量, 则有界且容量不可更改, 如果没有指定, 则是无界
使用一把锁和 notEmpty / notFull 两个条件来保证线程安全, 插入和删除用的是同一把锁
可有界可无界 / 不允许 null / 线程安全