JDK 队列工具 (Queue Deque BlockingQueue BlockingDeque TransferQueue)

博文目录

文章目录


在这里插入图片描述

接口定义

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 元素

扫描二维码关注公众号,回复: 14521942 查看本文章
头操作(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, 基于数组存储的堆(二叉树)的无界非阻塞队列, 非线程安全)

进阶JavaSE-PriorityQueue优先级队列

// 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 / 线程安全

猜你喜欢

转载自blog.csdn.net/mrathena/article/details/125192592
今日推荐