Java中Queue接口的学习
Queue接口
非阻塞队列
LinkedList
在Java中,Deque
(双端队列)是一个接口,而LinkedList
类实现了Deque
接口,因此它也可以被用作双端队列。
特点
- 双端操作:
LinkedList
作为Deque
的实现,支持从两端添加、删除和访问元素。 - 动态扩容:其容量可以根据需要动态增长,无需预先指定大小。
- 非线程安全:
LinkedList
不是线程安全的,如果需要在多线程环境下使用,需要进行外部同步。 - 允许null元素:
LinkedList
允许包含null元素。
底层结构
- 链表结构:
LinkedList
的底层是通过双向链表实现的。每个节点(Node)都包含三个部分:元素值(item)、指向前一个节点的引用(prev)和指向后一个节点的引用(next)。 - 头尾指针:
LinkedList
维护了两个指针,分别指向链表的头部(first)和尾部(last),以便于从两端进行操作。
常用用法
作为Deque
的实现,LinkedList
提供了丰富的双端队列操作方法:
-
添加元素:
addFirst(E e)
:在双端队列的头部添加元素。addLast(E e)
:在双端队列的尾部添加元素。offerFirst(E e)
:在双端队列的头部添加元素,并返回true
(因为总是可以添加)。offerLast(E e)
:在双端队列的尾部添加元素,并返回true
(因为总是可以添加)。
-
删除元素:
removeFirst()
:删除并返回双端队列头部的元素,如果队列为空,则抛出NoSuchElementException
。removeLast()
:删除并返回双端队列尾部的元素,如果队列为空,则抛出NoSuchElementException
。pollFirst()
:删除并返回双端队列头部的元素,如果队列为空,则返回null
。pollLast()
:删除并返回双端队列尾部的元素,如果队列为空,则返回null
。
-
获取元素:
getFirst()
:获取但不删除双端队列头部的元素,如果队列为空,则抛出NoSuchElementException
。getLast()
:获取但不删除双端队列尾部的元素,如果队列为空,则抛出NoSuchElementException
。peekFirst()
:获取但不删除双端队列头部的元素,如果队列为空,则返回null
。peekLast()
:获取但不删除双端队列尾部的元素,如果队列为空,则返回null
。
-
其他操作:
isEmpty()
:检查双端队列是否为空。size()
:返回双端队列中的元素数量。contains(Object o)
:检查双端队列中是否包含指定的元素。
由于LinkedList
实现了List
接口,它还提供了List
接口的所有方法,如get(int index)
、set(int index, E element)
、add(int index, E element)
等,但这些方法主要用于按索引访问和修改元素,与双端队列的特性关系不大。
综上所述,LinkedList
作为Deque
的实现,在Java中提供了灵活的双端队列操作,适用于需要频繁从两端添加、删除和访问元素的场景。
ArrayDeque
Java中的ArrayDeque
是一个基于数组实现的双端队列,它具有一系列独特的特点、底层结构和常用用法。以下是详细的解析:
特点
- 无容量大小限制:
ArrayDeque
的容量是按需增长的,不会受到初始容量的限制。 - 非线程安全:
ArrayDeque
不是线程安全的,它没有同步策略,不支持多线程安全访问。因此,在多线程环境下使用时需要外部同步。 - 高效性:
ArrayDeque
在作为栈(stack)使用时,性能优于Stack
;在作为队列(queue)使用时,性能优于LinkedList
。 - 双端操作:
ArrayDeque
支持从两端添加和移除元素,提供了addFirst
、addLast
、removeFirst
、removeLast
等方法。 - 不能存储null:
ArrayDeque
不允许存储null元素,尝试添加null会抛出NullPointerException
。 - fail-fast迭代器:
ArrayDeque
的迭代器是快速失败(fail-fast)的,但在并发环境下,程序不能依赖这个特性来检测并发修改。
底层结构
ArrayDeque
的底层结构是一个动态扩容的数组(Object[]
),其中包含了几个关键属性:
- elements:用于存储队列元素的数组,其大小总是2的幂次方,以便于通过位运算进行高效的索引计算。
- head:队列的头部位置索引,表示出队或弹出栈时的元素位置。
- tail:队列的尾部位置索引,表示入队或进栈时的元素位置,且
tail
位总是空的,以便于插入新元素。
ArrayDeque
通过维护head
和tail
两个索引来实现双端队列的功能,并通过动态扩容机制来应对元素的增加。
常用用法
ArrayDeque
提供了丰富的操作方法,以下是一些常用的方法:
-
添加元素:
addFirst(E e)
:在双端队列的头部添加元素。addLast(E e)
:在双端队列的尾部添加元素。offerFirst(E e)
:在双端队列的头部添加元素,并返回是否添加成功。offerLast(E e)
:在双端队列的尾部添加元素,并返回是否添加成功。
-
删除元素:
removeFirst()
:删除并返回双端队列头部的元素,如果队列为空,则抛出NoSuchElementException
。removeLast()
:删除并返回双端队列尾部的元素,如果队列为空,则抛出NoSuchElementException
。pollFirst()
:删除并返回双端队列头部的元素,如果队列为空,则返回null
。pollLast()
:删除并返回双端队列尾部的元素,如果队列为空,则返回null
。
-
获取元素:
getFirst()
:获取但不删除双端队列头部的元素,如果队列为空,则抛出NoSuchElementException
。getLast()
:获取但不删除双端队列尾部的元素,如果队列为空,则抛出NoSuchElementException
。
-
检查元素:
peekFirst()
:获取但不删除双端队列头部的元素,如果队列为空,则返回null
。peekLast()
:获取但不删除双端队列尾部的元素,如果队列为空,则返回null
。
ArrayDeque
的这些常用方法使得它既可以作为栈使用(只操作头部),也可以作为队列使用(只操作尾部或头部),提供了灵活的数据结构支持。
PriorityQueue
Java中的PriorityQueue
是一个非常重要的数据结构,它基于优先级对元素进行排序,而不是基于元素的插入顺序。
特点
-
优先级排序:
PriorityQueue
会根据元素的优先级进行排序,而不是按照元素的插入顺序。默认情况下,元素按照其自然顺序(如整数、字符串等)进行排序,但也可以通过提供自定义的Comparator
来改变排序规则。 -
无界队列:
PriorityQueue
是一个无界队列,即其容量可以动态增长,以满足存储需求。不过,由于它是基于数组实现的,实际上会受到JVM内存的限制。 -
线程不安全:
PriorityQueue
不是线程安全的,如果在多线程环境下使用,需要外部同步机制来保证线程安全。对于并发场景,可以考虑使用PriorityBlockingQueue
。 -
不允许null元素:
PriorityQueue
不允许插入null元素,尝试插入null会抛出NullPointerException
。 -
基于堆的实现:
PriorityQueue
的底层是通过堆(默认是小根堆)来实现的,这使得其插入和删除操作的时间复杂度均为O(log n),其中n是队列中元素的数量。
底层结构
在Java中,PriorityQueue
的底层数据结构是一个动态数组,但它按照堆的性质来组织元素。堆是一种特殊的完全二叉树,其中每个父节点的值都小于或等于(对于小根堆)其子节点的值,或者大于或等于(对于大根堆)其子节点的值。PriorityQueue
默认使用小根堆,但可以通过构造函数指定使用大根堆(通过提供自定义的Comparator
)。
常用用法
-
插入元素:
- 使用
add(E e)
或offer(E e)
方法将元素插入到PriorityQueue
中。插入后,元素会根据其优先级进行排序。
- 使用
-
删除并获取队首元素:
- 使用
poll()
方法删除并返回PriorityQueue
中优先级最高的元素(即队首元素)。如果队列为空,则返回null。
- 使用
-
获取队首元素但不删除:
- 使用
peek()
方法获取PriorityQueue
中优先级最高的元素,但不从队列中删除它。如果队列为空,则返回null。
- 使用
-
检查队列是否为空:
- 使用
isEmpty()
方法检查PriorityQueue
是否为空。
- 使用
-
获取队列大小:
- 使用
size()
方法获取PriorityQueue
中元素的数量。
- 使用
-
遍历队列:
- 可以使用
iterator()
方法获取PriorityQueue
的迭代器,然后通过迭代器遍历队列中的元素。但需要注意的是,由于PriorityQueue
是基于优先级的,遍历顺序可能与元素的插入顺序不同。
- 可以使用
-
自定义排序规则:
- 通过在创建
PriorityQueue
时提供自定义的Comparator
,可以改变元素的排序规则。这样,元素就可以根据自定义的优先级进行排序。
- 通过在创建
综上所述,PriorityQueue
是Java中一个非常有用的数据结构,它基于优先级对元素进行排序,并支持动态扩容。通过合理的使用,可以在许多场景下提高程序的效率和性能。
ConcurrentLinkedQueue
Java中的ConcurrentLinkedQueue
是一个线程安全的无界队列,它实现了Queue
接口,并且采用链表数据结构来存储元素。
特点
-
线程安全:
ConcurrentLinkedQueue
使用了一些并发技术(如CAS算法)来保证多线程环境下的安全性,允许多个线程同时对其进行读写操作,无需额外的同步控制。 -
无界队列:
ConcurrentLinkedQueue
没有容量限制,可以根据需要动态地添加元素,适用于生产者-消费者模式中的任务队列。 -
非阻塞:由于采用了无锁算法,
ConcurrentLinkedQueue
中的操作都是非阻塞的,这在高并发场景下非常有用,因为它可以减少线程之间的等待和竞争。 -
高效性能:
ConcurrentLinkedQueue
通过高效的并发算法,如CAS操作,来提高系统的并发能力和性能。这使得它在处理高并发场景下的元素存储和获取时,表现非常出色。 -
内存效率:
ConcurrentLinkedQueue
的内存效率很高,因为它只存储当前队列中的元素,而不会存储已经从队列中移除的元素。 -
不支持null元素:需要注意的是,
ConcurrentLinkedQueue
不支持存储null元素。
底层结构
ConcurrentLinkedQueue
的底层结构是基于链表的无边界队列。它主要包含两个指针(或称为节点引用):head
和tail
,分别指向链表的头部和尾部。链表的每个节点都是一个内部类Node
的实例,Node
类中包含两个主要属性:item
用于存储节点的值,next
用于指向链表中的下一个节点。当进行元素的添加或删除操作时,head
和tail
指针会相应地更新,以维护队列的先进先出(FIFO)顺序。
常用用法
-
创建队列:使用无参构造函数创建
ConcurrentLinkedQueue
对象。ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
-
添加元素:使用
add(E e)
或offer(E e)
方法将元素添加到队列的尾部。queue.add(1); queue.offer(2);
-
移除元素:可以使用
poll()
方法从队列的头部移除并返回元素,如果队列为空则返回null。也可以使用remove(Object o)
方法移除队列中第一次出现的指定元素(如果存在),成功时返回true,否则返回false。Integer removed = queue.poll(); // 移除并返回头部元素 boolean success = queue.remove(1); // 尝试移除元素1
-
获取元素:使用
peek()
方法获取队列头部的元素但不移除它,如果队列为空则返回null。Integer headElement = queue.peek();
-
检查队列状态:使用
isEmpty()
方法检查队列是否为空,使用size()
方法获取队列中元素的数量(但请注意,由于并发性质,size()
方法提供的是一个估计值)。boolean isEmpty = queue.isEmpty(); int size = queue.size();
综上所述,ConcurrentLinkedQueue
是一个高效、线程安全、无界的队列实现,适合用于高并发场景下的元素存储和获取。在使用时,需要注意其不支持存储null元素和阻塞操作的特点。
阻塞队列
Array Blocking Queue
Java中的ArrayBlockingQueue
是一个基于数组实现的有界阻塞队列,它在多线程环境下提供了线程安全的队列操作。
特点
-
有界性:
ArrayBlockingQueue
在创建时需要指定一个固定的大小,之后这个大小就不能再改变了。这意味着队列的容量是有限的,不会无限制地增长,从而避免了内存溢出的问题。 -
阻塞性:当队列满时,如果再有新的元素试图加入队列,那么这个操作会被阻塞,直到队列中有空间可用;同样地,如果队列为空,那么从队列中取元素的操作也会被阻塞,直到队列中有元素可供消费。这种特性使得
ArrayBlockingQueue
非常适合作为生产者-消费者模式中的缓冲区。 -
线程安全性:
ArrayBlockingQueue
是线程安全的,它通过内部锁机制(如ReentrantLock
)保证了在多线程环境下的安全性。因此,在多线程环境中,你可以放心地使用它而不需要担心数据的一致性问题。 -
高效性:由于
ArrayBlockingQueue
是基于数组实现的,因此在某些操作(如随机访问)上可能比基于链表的队列(如LinkedBlockingQueue
)更高效。但是,由于入队和出队操作共享同一把锁,所以在高并发场景下可能会成为性能瓶颈。
底层结构
ArrayBlockingQueue
的底层结构主要包括以下几个部分:
- 数组:用于存储队列元素的数组,一旦初始化,其容量就不可更改。
- 索引:通常包括两个索引,一个用于指向队列头部的元素(takeIndex),另一个用于指向队列尾部的下一个插入位置(putIndex)。这两个索引用于在数组中高效地管理队列的入队和出队操作。
- 锁和条件变量:使用
ReentrantLock
作为内部锁来控制对队列的并发访问,并通过Condition
(如notEmpty
和notFull
)来实现阻塞等待和唤醒机制。
常用用法
ArrayBlockingQueue
提供了丰富的操作方法来支持队列的入队、出队以及查询等操作。以下是一些常用的方法:
-
构造方法:
ArrayBlockingQueue(int capacity)
:创建一个具有指定容量(以整数形式指定)的ArrayBlockingQueue
。ArrayBlockingQueue(int capacity, boolean fair)
:创建一个具有指定容量和指定公平性设置的ArrayBlockingQueue
。公平性设置为true
时,线程将大致按照它们被添加到队列的顺序(FIFO)来获得访问权;设置为false
时,则不保证此顺序。
-
入队操作:
boolean add(E e)
:将指定元素插入此队列的尾部(如果立即可行且不会违反容量限制),返回true
;如果此队列已满,则抛出IllegalStateException
。boolean offer(E e)
:将指定元素插入此队列的尾部(如果队列未满),返回true
;如果此队列已满,则返回false
。void put(E e) throws InterruptedException
:将指定元素插入此队列的尾部,如果队列满,则等待可用的空间。
-
出队操作:
E remove()
:检索并移除此队列的头部,如果此队列为空,则抛出NoSuchElementException
。E poll()
:检索并移除此队列的头部;如果此队列为空,则返回null
。E take() throws InterruptedException
:检索并移除此队列的头部,在元素变得可用之前,其他线程会在此处阻塞。
-
其他操作:
int size()
:返回队列中的元素数量。boolean isEmpty()
:如果队列为空,则返回true
。int remainingCapacity()
:返回队列中剩余可用空间的容量。
通过以上方法,ArrayBlockingQueue
在多线程环境中提供了一种高效、安全且易于使用的队列实现方式。
LinkedBlockingQueue
Java中的LinkedBlockingQueue
是一个线程安全的阻塞队列,它实现了BlockingQueue
接口。
特点
-
线程安全:
LinkedBlockingQueue
内部通过锁机制(如ReentrantLock
)保证了在多线程环境下的线程安全性,因此可以在多个线程之间共享使用而无需额外的同步控制。 -
阻塞性:当队列满时,尝试向队列中添加元素的线程会被阻塞,直到队列中有空间可用;同样地,当队列为空时,尝试从队列中移除元素的线程也会被阻塞,直到队列中有元素可供移除。
-
可选容量限制:
LinkedBlockingQueue
可以在创建时指定容量,如果不指定,则默认容量为Integer.MAX_VALUE
,即无界队列。有界队列在达到容量限制时会阻塞插入操作,直到队列中有空间可用。 -
公平性:
LinkedBlockingQueue
支持公平性设置(通过构造函数中的fair
参数指定),当设置为true
时,线程将按照它们被添加到队列的顺序(即FIFO)来获取访问权,这有助于减少饥饿现象。但需要注意的是,公平性通常会降低吞吐量。 -
基于链表实现:
LinkedBlockingQueue
内部使用链表结构来存储元素,这使得它在进行插入和删除操作时具有较高的效率,尤其是在队列的头部和尾部进行操作时。
底层结构
LinkedBlockingQueue
的底层结构主要包括以下几个部分:
- 链表节点:每个节点都包含元素值、指向前一个节点的引用以及指向下一个节点的引用,从而构成了一个双向链表。
- 锁和条件变量:
LinkedBlockingQueue
使用了两把锁(putLock
和takeLock
)来控制并发访问,分别用于控制插入和删除操作。同时,它还使用了两个条件变量(notFull
和notEmpty
)来实现线程的阻塞和唤醒。 - 队列头部和尾部:分别通过
head
和last
指针来指示队列的头部和尾部元素。 - 计数器:使用一个计数器(
count
)来记录队列中元素的数量,以便快速判断队列是否为空或已满。
常用用法
LinkedBlockingQueue
提供了丰富的操作方法来支持队列的入队、出队以及查询等操作。以下是一些常用的方法:
-
入队操作:
boolean add(E e)
:向队列尾部添加一个元素,如果队列已满,则抛出IllegalStateException
异常。boolean offer(E e)
:向队列尾部添加一个元素,如果队列已满,则返回false
。void put(E e) throws InterruptedException
:向队列尾部添加一个元素,如果队列已满,则线程被阻塞直到队列有空间可用。
-
出队操作:
E remove()
:移除并返回队列头部的元素,如果队列为空,则抛出NoSuchElementException
。E poll()
:移除并返回队列头部的元素,如果队列为空,则返回null
。E take() throws InterruptedException
:移除并返回队列头部的元素,如果队列为空,则线程被阻塞直到队列有元素可用。
-
其他操作:
int size()
:返回队列中元素的个数。boolean isEmpty()
:如果队列为空,则返回true
。void clear()
:清空队列中的所有元素。E peek()
:返回队列头部的元素,但不移除。
这些操作方法使得LinkedBlockingQueue
在多线程环境下能够高效地进行数据的存储和传递,特别适合于实现生产者-消费者模式。
PriorityBlockingQueue
Java中的PriorityBlockingQueue
是一个支持优先级的无界阻塞队列,它基于优先级堆(通常是二叉堆)实现,确保了队列中元素的有序性。
特点
-
优先级排序:队列中的元素会根据其自然排序(如果元素实现了
Comparable
接口)或者通过构造函数传入的Comparator
进行排序。这意味着每次从队列中取出元素时,都是取出当前优先级最高的元素。 -
无界但受系统资源限制:虽然
PriorityBlockingQueue
被设计为无界队列,但实际上它的容量受到系统可用内存的限制。当元素不断被添加到队列中,且队列中的元素数量超过系统所能处理的范围时,可能会导致内存溢出。 -
线程安全:作为
BlockingQueue
接口的实现,PriorityBlockingQueue
提供了线程安全的队列操作,允许多个线程同时访问队列而无需进行外部同步。 -
阻塞特性:虽然
PriorityBlockingQueue
是无界的,但在某些场景下(如使用take()
或带超时的poll()
方法时),如果队列为空,则尝试从队列中取元素的线程会被阻塞,直到队列中有元素可用。 -
灵活性:允许在队列实例化时指定初始容量和比较器,以及在不指定初始容量时使用默认容量(通常为11)。
底层结构
PriorityBlockingQueue
的底层结构主要包括以下几个部分:
-
二叉堆:使用二叉堆(通常是最小堆)来存储队列中的元素,确保每次出队的都是优先级最高的元素。二叉堆是一种特殊的完全二叉树,其中每个父节点的值都小于或等于其子节点的值(在最小堆中)。
-
动态扩容:由于
PriorityBlockingQueue
是无界的,当元素数量超过当前数组容量时,它会自动进行扩容操作。扩容操作会创建一个新的、更大的数组,并将旧数组中的元素复制到新数组中。为了避免在扩容过程中发生并发问题,扩容操作会先释放锁,然后通过无锁化的CAS(Compare-And-Swap)操作来确保只有一个线程可以成功扩容。 -
锁和条件变量:使用
ReentrantLock
作为内部锁来控制对队列的并发访问,并通过Condition
(如notEmpty
)来实现线程的阻塞和唤醒。
常用用法
PriorityBlockingQueue
提供了多种方法来支持队列的入队、出队以及查询等操作。以下是一些常用的方法:
-
入队操作:
boolean add(E e)
:将指定元素插入此队列,如果此队列已满(实际上对于PriorityBlockingQueue
来说,这几乎不可能发生),则抛出IllegalStateException
。但请注意,由于PriorityBlockingQueue
是无界的,所以这个方法几乎不会抛出异常。boolean offer(E e)
:将指定元素插入此队列,如果此队列有空间(即未超过系统内存限制),则返回true
。void put(E e)
:将指定元素插入此队列,如果队列满(尽管实际上不太可能),则等待可用的空间。但由于PriorityBlockingQueue
是无界的,所以这个方法通常不会阻塞。
-
出队操作:
E remove()
:检索并移除此队列的头部(即优先级最高的元素),如果此队列为空,则抛出NoSuchElementException
。E poll()
:检索并移除此队列的头部,如果此队列为空,则返回null
。E take()
:检索并移除此队列的头部,在元素变得可用之前,其他线程会在此处阻塞。
-
其他操作:
int size()
:返回队列中的元素数量。boolean isEmpty()
:如果队列为空,则返回true
。Iterator<E> iterator()
:返回队列中元素的迭代器,但需要注意的是,迭代器提供的是弱一致性的视图,它反映的是迭代器创建时队列的状态,而不保证反映队列的当前状态。
综上所述,PriorityBlockingQueue
是一个功能强大且灵活的优先级队列实现,特别适用于需要按优先级处理任务的场景。
Delay Queue
Java中的DelayQueue
是一个支持延时获取元素的阻塞队列,它实现了BlockingQueue
接口。
特点
-
延时性:队列中的元素必须实现
Delayed
接口,该接口要求元素提供剩余延迟时间(通过getDelay
方法)。只有当元素的剩余延迟时间为零或负数时,该元素才能从队列中取出。 -
无界性:
DelayQueue
是一个无界队列,这意味着它可以无限期地存储元素,直到系统内存耗尽。 -
有序性:队列中的元素按照它们的延迟时间进行排序,延迟时间最短(即最早到期)的元素会被放在队列的头部。
-
阻塞性:当队列为空时,尝试从队列中取出元素的线程会被阻塞,直到有元素到期。
-
线程安全:
DelayQueue
是线程安全的,允许多个线程同时访问队列而无需进行外部同步。
底层结构
DelayQueue
的底层结构主要基于PriorityQueue
实现,但添加了一些额外的机制来支持延时和阻塞功能。具体来说,它包含以下几个关键部分:
-
PriorityQueue:用于存储队列中的元素,并根据元素的延迟时间进行排序。
PriorityQueue
内部实现了一个小顶堆,以确保每次都能快速找到延迟时间最短的元素。 -
ReentrantLock:用于保证队列操作的线程安全。所有对队列的修改操作(如入队、出队)都需要先获取这个锁。
-
Condition:
DelayQueue
使用Condition
对象(通过ReentrantLock
的newCondition
方法创建)来实现线程的阻塞和唤醒。当队列为空或没有到期的元素时,尝试从队列中取出元素的线程会在Condition
上等待,直到有元素到期并被唤醒。 -
Leader-Follower模式:
DelayQueue
采用了一种称为“Leader-Follower”的线程等待模式。当队列中有元素时,第一个调用take()
方法的线程会成为leader线程,并在Condition
上等待队列头结点剩余的延迟时间。其他线程则成为follower线程,并在Condition
上无限期等待。当leader线程苏醒并获取到元素后,它会唤醒一个在Condition
上等待的follower线程,该线程可能成为新的leader线程。
常用用法
DelayQueue
常用于实现定时任务调度,例如任务调度器中,可以将定时任务封装成Delayed
对象放入DelayQueue
中,然后由一个线程轮询DelayQueue
,当延迟时间到达时执行相应的任务。以下是一些常用的方法:
-
入队操作:
boolean add(E e)
:将指定元素插入此队列,如果此队列已满(对于DelayQueue
来说,这几乎不可能发生),则抛出IllegalStateException
。但请注意,由于DelayQueue
是无界的,所以这个方法几乎不会抛出异常。boolean offer(E e)
:将指定元素插入此队列,如果此队列有空间(即未超过系统内存限制),则返回true
。void put(E e)
:将指定元素插入此队列,如果队列满(尽管实际上不太可能),则等待可用的空间。但由于DelayQueue
是无界的,所以这个方法通常不会阻塞。
-
出队操作:
E take()
:检索并移除此队列的头部(即延迟时间最短且已到期的元素),在元素变得可用之前,其他线程会在此处阻塞。E poll()
:检索并移除此队列的头部(如果已到期),如果此队列为空,则返回null
。
-
其他操作:
int size()
:返回队列中的元素数量。boolean isEmpty()
:如果队列为空,则返回true
。
需要注意的是,由于DelayQueue
是无界的,因此在使用时需要特别注意内存管理,避免因为元素过多而导致内存溢出。同时,由于DelayQueue
中的元素必须实现Delayed
接口,因此在创建元素时需要指定其延迟时间。
Synchronous Queue
Java中的SynchronousQueue
是一个特殊的阻塞队列,它的主要特点是内部没有容量,即每个插入操作必须等待另一个线程的删除操作,反之亦然。
特点
-
零容量:
SynchronousQueue
的容量为0,这意味着它不能存储任何元素。插入操作和删除操作是紧密耦合的,即生产者线程必须等待消费者线程来取走元素,反之亦然。 -
直接传递:队列中的元素是直接从生产者线程传递给消费者线程的,没有任何中间存储。这种机制适用于需要立即传递数据的情况。
-
支持公平性:
SynchronousQueue
提供了两种构造方法,一种是默认的非公平模式,另一种是公平模式。在公平模式下,等待时间最长的线程会优先得到插入或删除元素的机会。 -
线程间协作:
SynchronousQueue
提供了一种线程间的协作机制,使得线程之间可以直接进行数据交换,而无需通过中间队列来缓冲。
底层结构
SynchronousQueue
的底层实现主要依赖于内部的一个Transferer
接口,该接口有两个具体的实现类:TransferStack
(基于栈实现,用于非公平模式)和TransferQueue
(基于队列实现,用于公平模式)。这些实现类通过维护一系列的内部节点(如请求节点和数据节点)来管理线程的插入和删除操作。
-
TransferStack:基于栈的非公平实现。当线程尝试插入或删除元素时,它会被包装成一个节点并压入栈中。栈顶元素代表当前正在等待的线程。
-
TransferQueue:基于队列的公平实现。与
TransferStack
类似,但它在内部维护了一个队列来管理等待的线程,确保等待时间最长的线程能够优先得到服务。
常用用法
SynchronousQueue
常用于需要立即进行线程间数据交换的场景,如生产者-消费者模型中的高并发通信。以下是一些常用方法:
-
put(E e):将指定元素插入队列中。如果队列为空(即没有等待的线程来取走元素),则当前线程会被阻塞,直到有另一个线程来取走元素。
-
take():从队列中取出一个元素。如果队列为空(即没有元素可取),则当前线程会被阻塞,直到有另一个线程来插入元素。
-
offer(E e, long timeout, TimeUnit unit):尝试将元素插入队列中,等待指定的时间。如果在等待时间内有线程来取走元素,则插入成功并返回
true
;否则,在超时后返回false
。 -
poll(long timeout, TimeUnit unit):尝试从队列中取出一个元素,等待指定的时间。如果在等待时间内有元素可取,则返回该元素;否则,在超时后返回
null
。
需要注意的是,由于SynchronousQueue
的容量为0,因此它不支持如peek
、element
等查看但不移除元素的操作,这些操作在SynchronousQueue
中是没有意义的。
SynchronousQueue
在Java并发编程中有广泛的应用,如线程池中的newCachedThreadPool
就使用了SynchronousQueue
来实现任务的快速调度和处理。此外,它还可以用于实现限流控制、异步任务结果传递等场景。
LinkedTransferQueue
Java中的LinkedTransferQueue
是一个基于链表的无界阻塞队列,它实现了BlockingQueue
和TransferQueue
接口,具有高效、低延迟以及直接的生产者-消费者交互等特点。
特点
-
无界性:
LinkedTransferQueue
是一个无界队列,它允许任意数量的元素被添加到队列中,而不用担心队列溢出的问题。 -
高效性:它支持高效的线程间数据传递,特别适合于需要高并发和低延迟的场景。
-
直接传递:
LinkedTransferQueue
提供了一种机制,使得生产者可以直接将元素传输给等待的消费者,而不需要将元素存储在队列中。这减少了不必要的上下文切换和内存使用。 -
避免无效通知:在某些其他阻塞队列中,线程可能会由于操作系统或JVM的原因而意外地提前唤醒(虚假唤醒)。
LinkedTransferQueue
使用自旋等优化技术来减少这种无效通知,从而提高效率。 -
混合支持非阻塞和阻塞操作:除了基本的插入(offer)、移除(poll)和检查(peek)等操作外,还提供了
transfer
、tryTransfer
等额外的方法,这些方法允许生产者和消费者之间进行更灵活的交互。
底层结构
LinkedTransferQueue
的底层结构是由单链表组成的双重队列结构,类似于SynchronousQueue
的公平模式。它使用两个指针(head和tail)来分别指向队列的头部和尾部,并通过CAS(Compare-And-Swap)原子操作来更新这两个指针。每个节点(Node)都包含数据项(item)、后继节点(next)、等待线程(waiter)等信息,并通过volatile
关键字保证多线程环境下的可见性。
常用用法
LinkedTransferQueue
提供了丰富的方法来支持生产者和消费者之间的交互,以下是一些常用方法:
-
transfer(E e):如果当前存在一个正在等待获取的消费者线程,则立即将元素e传输给该消费者;否则,将元素e插入队列尾部,并等待直到有消费者线程取走该元素。这是一个阻塞操作。
-
tryTransfer(E e):如果当前存在一个正在等待获取的消费者线程,则立即将元素e传输给该消费者;如果不存在,则返回
false
,并且不将元素e插入队列。这是一个非阻塞操作。 -
tryTransfer(E e, long timeout, TimeUnit unit):如果当前存在一个正在等待获取的消费者线程,则立即将元素e传输给该消费者;如果不存在,则将元素e插入队列尾部,并等待指定的时间。如果在指定时间内元素e被消费者线程取走,则返回
true
;否则,返回false
,并且从队列中移除元素e。 -
poll():移除并返回队列头部的元素,如果队列为空,则返回
null
。这是一个非阻塞操作。 -
take():移除并返回队列头部的元素,如果队列为空,则阻塞直到有元素可用。
-
peek():返回队列头部的元素,但不移除。如果队列为空,则返回
null
。 -
size():由于队列的异步特性,检测当前队列的元素个数需要逐一迭代,可能会得到一个不太准确的结果,尤其是在遍历时有可能队列发生更改。因此,这个方法的使用需要谨慎。
-
hasWaitingConsumer():判断是否存在正在等待获取元素的消费者线程。
LinkedTransferQueue
的这些方法使得它特别适合于那些需要高效、低延迟以及直接生产者-消费者交互的并发场景,如工作窃取算法或任务传递系统等。
LinkedBlockingQueue
Java中的LinkedBlockingQueue
是一个线程安全的阻塞队列,它实现了BlockingQueue
接口,并基于链表结构实现。
特点
- 线程安全:
LinkedBlockingQueue
内部使用了锁机制来保证多线程环境下的线程安全,因此可以被多个线程同时使用而不需要额外的同步措施。 - 阻塞特性:当队列满时,插入操作(如
put
方法)会被阻塞,直到队列中有空间可用;当队列空时,移除操作(如take
方法)会被阻塞,直到队列中有元素可供移除。 - 可选容量限制:
LinkedBlockingQueue
可以在初始化时指定最大容量,如果未指定,则默认容量为Integer.MAX_VALUE
,即无界队列。 - 公平性:
LinkedBlockingQueue
可以选择是否按照FIFO(先进先出)原则对等待在队列中的线程进行调度。如果设置为公平模式(通过设置构造函数中的fair
参数为true
),则线程会按照请求的顺序被唤醒,但请注意,公平模式可能会降低吞吐量。 - 基于链表:内部使用链表来存储元素,这使得它在插入和删除元素时具有较高的效率。
底层结构
LinkedBlockingQueue
的底层结构主要由以下几个部分组成:
- 链表节点(Node):每个节点包含元素数据、前驱节点引用和后继节点引用。
- 头节点(head)和尾节点(last):分别指向队列的首部和尾部,用于快速定位队列的首尾元素。
- 锁机制:通常使用两把锁,一把用于控制插入操作(putLock),另一把用于控制删除操作(takeLock),以提高并发性能。此外,还可能使用条件变量(notEmpty和notFull)来实现线程的阻塞和唤醒。
常用用法
LinkedBlockingQueue
提供了多种方法来支持队列的基本操作以及阻塞操作:
- 插入元素:
add(E e)
:向队列尾部添加一个元素,如果队列已满,则抛出IllegalStateException
异常。offer(E e)
:向队列尾部添加一个元素,如果队列已满,则返回false
。put(E e)
:向队列尾部添加一个元素,如果队列已满,则线程被阻塞直到队列有空间可用。
- 移除元素:
poll()
:移除并返回队列头部的元素,如果队列为空,则返回null
。take()
:移除并返回队列头部的元素,如果队列为空,则线程被阻塞直到有元素可用。
- 查看元素:
peek()
:返回队列头部的元素,但不移除。如果队列为空,则返回null
。
- 其他操作:
size()
:返回队列中元素的个数。remainingCapacity()
:返回队列的剩余容量。isEmpty()
:判断队列是否为空。clear()
:清空队列中的所有元素。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)