Java中Queue接口的学习

Queue接口


非阻塞队列

LinkedList

在Java中,Deque(双端队列)是一个接口,而LinkedList类实现了Deque接口,因此它也可以被用作双端队列。

特点

  • 双端操作LinkedList作为Deque的实现,支持从两端添加、删除和访问元素。
  • 动态扩容:其容量可以根据需要动态增长,无需预先指定大小。
  • 非线程安全LinkedList不是线程安全的,如果需要在多线程环境下使用,需要进行外部同步。
  • 允许null元素LinkedList允许包含null元素。

底层结构

  • 链表结构LinkedList的底层是通过双向链表实现的。每个节点(Node)都包含三个部分:元素值(item)、指向前一个节点的引用(prev)和指向后一个节点的引用(next)。
  • 头尾指针LinkedList维护了两个指针,分别指向链表的头部(first)和尾部(last),以便于从两端进行操作。

常用用法

作为Deque的实现,LinkedList提供了丰富的双端队列操作方法:

  1. 添加元素

    • addFirst(E e):在双端队列的头部添加元素。
    • addLast(E e):在双端队列的尾部添加元素。
    • offerFirst(E e):在双端队列的头部添加元素,并返回true(因为总是可以添加)。
    • offerLast(E e):在双端队列的尾部添加元素,并返回true(因为总是可以添加)。
  2. 删除元素

    • removeFirst():删除并返回双端队列头部的元素,如果队列为空,则抛出NoSuchElementException
    • removeLast():删除并返回双端队列尾部的元素,如果队列为空,则抛出NoSuchElementException
    • pollFirst():删除并返回双端队列头部的元素,如果队列为空,则返回null
    • pollLast():删除并返回双端队列尾部的元素,如果队列为空,则返回null
  3. 获取元素

    • getFirst():获取但不删除双端队列头部的元素,如果队列为空,则抛出NoSuchElementException
    • getLast():获取但不删除双端队列尾部的元素,如果队列为空,则抛出NoSuchElementException
    • peekFirst():获取但不删除双端队列头部的元素,如果队列为空,则返回null
    • peekLast():获取但不删除双端队列尾部的元素,如果队列为空,则返回null
  4. 其他操作

    • 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是一个基于数组实现的双端队列,它具有一系列独特的特点、底层结构和常用用法。以下是详细的解析:

特点

  1. 无容量大小限制ArrayDeque的容量是按需增长的,不会受到初始容量的限制。
  2. 非线程安全ArrayDeque不是线程安全的,它没有同步策略,不支持多线程安全访问。因此,在多线程环境下使用时需要外部同步。
  3. 高效性ArrayDeque在作为栈(stack)使用时,性能优于Stack;在作为队列(queue)使用时,性能优于LinkedList
  4. 双端操作ArrayDeque支持从两端添加和移除元素,提供了addFirstaddLastremoveFirstremoveLast等方法。
  5. 不能存储nullArrayDeque不允许存储null元素,尝试添加null会抛出NullPointerException
  6. fail-fast迭代器ArrayDeque的迭代器是快速失败(fail-fast)的,但在并发环境下,程序不能依赖这个特性来检测并发修改。

底层结构

ArrayDeque的底层结构是一个动态扩容的数组(Object[]),其中包含了几个关键属性:

  • elements:用于存储队列元素的数组,其大小总是2的幂次方,以便于通过位运算进行高效的索引计算。
  • head:队列的头部位置索引,表示出队或弹出栈时的元素位置。
  • tail:队列的尾部位置索引,表示入队或进栈时的元素位置,且tail位总是空的,以便于插入新元素。

ArrayDeque通过维护headtail两个索引来实现双端队列的功能,并通过动态扩容机制来应对元素的增加。

常用用法

ArrayDeque提供了丰富的操作方法,以下是一些常用的方法:

  1. 添加元素

    • addFirst(E e):在双端队列的头部添加元素。
    • addLast(E e):在双端队列的尾部添加元素。
    • offerFirst(E e):在双端队列的头部添加元素,并返回是否添加成功。
    • offerLast(E e):在双端队列的尾部添加元素,并返回是否添加成功。
  2. 删除元素

    • removeFirst():删除并返回双端队列头部的元素,如果队列为空,则抛出NoSuchElementException
    • removeLast():删除并返回双端队列尾部的元素,如果队列为空,则抛出NoSuchElementException
    • pollFirst():删除并返回双端队列头部的元素,如果队列为空,则返回null
    • pollLast():删除并返回双端队列尾部的元素,如果队列为空,则返回null
  3. 获取元素

    • getFirst():获取但不删除双端队列头部的元素,如果队列为空,则抛出NoSuchElementException
    • getLast():获取但不删除双端队列尾部的元素,如果队列为空,则抛出NoSuchElementException
  4. 检查元素

    • peekFirst():获取但不删除双端队列头部的元素,如果队列为空,则返回null
    • peekLast():获取但不删除双端队列尾部的元素,如果队列为空,则返回null

ArrayDeque的这些常用方法使得它既可以作为栈使用(只操作头部),也可以作为队列使用(只操作尾部或头部),提供了灵活的数据结构支持。

PriorityQueue

Java中的PriorityQueue是一个非常重要的数据结构,它基于优先级对元素进行排序,而不是基于元素的插入顺序。

特点

  1. 优先级排序PriorityQueue会根据元素的优先级进行排序,而不是按照元素的插入顺序。默认情况下,元素按照其自然顺序(如整数、字符串等)进行排序,但也可以通过提供自定义的Comparator来改变排序规则。

  2. 无界队列PriorityQueue是一个无界队列,即其容量可以动态增长,以满足存储需求。不过,由于它是基于数组实现的,实际上会受到JVM内存的限制。

  3. 线程不安全PriorityQueue不是线程安全的,如果在多线程环境下使用,需要外部同步机制来保证线程安全。对于并发场景,可以考虑使用PriorityBlockingQueue

  4. 不允许null元素PriorityQueue不允许插入null元素,尝试插入null会抛出NullPointerException

  5. 基于堆的实现PriorityQueue的底层是通过堆(默认是小根堆)来实现的,这使得其插入和删除操作的时间复杂度均为O(log n),其中n是队列中元素的数量。

底层结构

在Java中,PriorityQueue的底层数据结构是一个动态数组,但它按照堆的性质来组织元素。堆是一种特殊的完全二叉树,其中每个父节点的值都小于或等于(对于小根堆)其子节点的值,或者大于或等于(对于大根堆)其子节点的值。PriorityQueue默认使用小根堆,但可以通过构造函数指定使用大根堆(通过提供自定义的Comparator)。

常用用法

  1. 插入元素

    • 使用add(E e)offer(E e)方法将元素插入到PriorityQueue中。插入后,元素会根据其优先级进行排序。
  2. 删除并获取队首元素

    • 使用poll()方法删除并返回PriorityQueue中优先级最高的元素(即队首元素)。如果队列为空,则返回null。
  3. 获取队首元素但不删除

    • 使用peek()方法获取PriorityQueue中优先级最高的元素,但不从队列中删除它。如果队列为空,则返回null。
  4. 检查队列是否为空

    • 使用isEmpty()方法检查PriorityQueue是否为空。
  5. 获取队列大小

    • 使用size()方法获取PriorityQueue中元素的数量。
  6. 遍历队列

    • 可以使用iterator()方法获取PriorityQueue的迭代器,然后通过迭代器遍历队列中的元素。但需要注意的是,由于PriorityQueue是基于优先级的,遍历顺序可能与元素的插入顺序不同。
  7. 自定义排序规则

    • 通过在创建PriorityQueue时提供自定义的Comparator,可以改变元素的排序规则。这样,元素就可以根据自定义的优先级进行排序。

综上所述,PriorityQueue是Java中一个非常有用的数据结构,它基于优先级对元素进行排序,并支持动态扩容。通过合理的使用,可以在许多场景下提高程序的效率和性能。

ConcurrentLinkedQueue

Java中的ConcurrentLinkedQueue是一个线程安全的无界队列,它实现了Queue接口,并且采用链表数据结构来存储元素。

特点

  1. 线程安全ConcurrentLinkedQueue使用了一些并发技术(如CAS算法)来保证多线程环境下的安全性,允许多个线程同时对其进行读写操作,无需额外的同步控制。

  2. 无界队列ConcurrentLinkedQueue没有容量限制,可以根据需要动态地添加元素,适用于生产者-消费者模式中的任务队列。

  3. 非阻塞:由于采用了无锁算法,ConcurrentLinkedQueue中的操作都是非阻塞的,这在高并发场景下非常有用,因为它可以减少线程之间的等待和竞争。

  4. 高效性能ConcurrentLinkedQueue通过高效的并发算法,如CAS操作,来提高系统的并发能力和性能。这使得它在处理高并发场景下的元素存储和获取时,表现非常出色。

  5. 内存效率ConcurrentLinkedQueue的内存效率很高,因为它只存储当前队列中的元素,而不会存储已经从队列中移除的元素。

  6. 不支持null元素:需要注意的是,ConcurrentLinkedQueue不支持存储null元素。

底层结构

ConcurrentLinkedQueue的底层结构是基于链表的无边界队列。它主要包含两个指针(或称为节点引用):headtail,分别指向链表的头部和尾部。链表的每个节点都是一个内部类Node的实例,Node类中包含两个主要属性:item用于存储节点的值,next用于指向链表中的下一个节点。当进行元素的添加或删除操作时,headtail指针会相应地更新,以维护队列的先进先出(FIFO)顺序。

常用用法

  1. 创建队列:使用无参构造函数创建ConcurrentLinkedQueue对象。

    复制代码
    ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
    
  2. 添加元素:使用add(E e)offer(E e)方法将元素添加到队列的尾部。

    复制代码
    queue.add(1);
    queue.offer(2);
    
  3. 移除元素:可以使用poll()方法从队列的头部移除并返回元素,如果队列为空则返回null。也可以使用remove(Object o)方法移除队列中第一次出现的指定元素(如果存在),成功时返回true,否则返回false。

    复制代码
    Integer removed = queue.poll(); // 移除并返回头部元素
    boolean success = queue.remove(1); // 尝试移除元素1
    
  4. 获取元素:使用peek()方法获取队列头部的元素但不移除它,如果队列为空则返回null。

    复制代码
    Integer headElement = queue.peek();
    
  5. 检查队列状态:使用isEmpty()方法检查队列是否为空,使用size()方法获取队列中元素的数量(但请注意,由于并发性质,size()方法提供的是一个估计值)。

    复制代码
    boolean isEmpty = queue.isEmpty();
    int size = queue.size();
    

综上所述,ConcurrentLinkedQueue是一个高效、线程安全、无界的队列实现,适合用于高并发场景下的元素存储和获取。在使用时,需要注意其不支持存储null元素和阻塞操作的特点。

阻塞队列

Array Blocking Queue

Java中的ArrayBlockingQueue是一个基于数组实现的有界阻塞队列,它在多线程环境下提供了线程安全的队列操作。

特点

  1. 有界性ArrayBlockingQueue在创建时需要指定一个固定的大小,之后这个大小就不能再改变了。这意味着队列的容量是有限的,不会无限制地增长,从而避免了内存溢出的问题。

  2. 阻塞性:当队列满时,如果再有新的元素试图加入队列,那么这个操作会被阻塞,直到队列中有空间可用;同样地,如果队列为空,那么从队列中取元素的操作也会被阻塞,直到队列中有元素可供消费。这种特性使得ArrayBlockingQueue非常适合作为生产者-消费者模式中的缓冲区。

  3. 线程安全性ArrayBlockingQueue是线程安全的,它通过内部锁机制(如ReentrantLock)保证了在多线程环境下的安全性。因此,在多线程环境中,你可以放心地使用它而不需要担心数据的一致性问题。

  4. 高效性:由于ArrayBlockingQueue是基于数组实现的,因此在某些操作(如随机访问)上可能比基于链表的队列(如LinkedBlockingQueue)更高效。但是,由于入队和出队操作共享同一把锁,所以在高并发场景下可能会成为性能瓶颈。

底层结构

ArrayBlockingQueue的底层结构主要包括以下几个部分:

  • 数组:用于存储队列元素的数组,一旦初始化,其容量就不可更改。
  • 索引:通常包括两个索引,一个用于指向队列头部的元素(takeIndex),另一个用于指向队列尾部的下一个插入位置(putIndex)。这两个索引用于在数组中高效地管理队列的入队和出队操作。
  • 锁和条件变量:使用ReentrantLock作为内部锁来控制对队列的并发访问,并通过Condition(如notEmptynotFull)来实现阻塞等待和唤醒机制。

常用用法

ArrayBlockingQueue提供了丰富的操作方法来支持队列的入队、出队以及查询等操作。以下是一些常用的方法:

  1. 构造方法

    • ArrayBlockingQueue(int capacity):创建一个具有指定容量(以整数形式指定)的ArrayBlockingQueue
    • ArrayBlockingQueue(int capacity, boolean fair):创建一个具有指定容量和指定公平性设置的ArrayBlockingQueue。公平性设置为true时,线程将大致按照它们被添加到队列的顺序(FIFO)来获得访问权;设置为false时,则不保证此顺序。
  2. 入队操作

    • boolean add(E e):将指定元素插入此队列的尾部(如果立即可行且不会违反容量限制),返回true;如果此队列已满,则抛出IllegalStateException
    • boolean offer(E e):将指定元素插入此队列的尾部(如果队列未满),返回true;如果此队列已满,则返回false
    • void put(E e) throws InterruptedException:将指定元素插入此队列的尾部,如果队列满,则等待可用的空间。
  3. 出队操作

    • E remove():检索并移除此队列的头部,如果此队列为空,则抛出NoSuchElementException
    • E poll():检索并移除此队列的头部;如果此队列为空,则返回null
    • E take() throws InterruptedException:检索并移除此队列的头部,在元素变得可用之前,其他线程会在此处阻塞。
  4. 其他操作

    • int size():返回队列中的元素数量。
    • boolean isEmpty():如果队列为空,则返回true
    • int remainingCapacity():返回队列中剩余可用空间的容量。

通过以上方法,ArrayBlockingQueue在多线程环境中提供了一种高效、安全且易于使用的队列实现方式。

LinkedBlockingQueue

Java中的LinkedBlockingQueue是一个线程安全的阻塞队列,它实现了BlockingQueue接口。

特点

  1. 线程安全LinkedBlockingQueue内部通过锁机制(如ReentrantLock)保证了在多线程环境下的线程安全性,因此可以在多个线程之间共享使用而无需额外的同步控制。

  2. 阻塞性:当队列满时,尝试向队列中添加元素的线程会被阻塞,直到队列中有空间可用;同样地,当队列为空时,尝试从队列中移除元素的线程也会被阻塞,直到队列中有元素可供移除。

  3. 可选容量限制LinkedBlockingQueue可以在创建时指定容量,如果不指定,则默认容量为Integer.MAX_VALUE,即无界队列。有界队列在达到容量限制时会阻塞插入操作,直到队列中有空间可用。

  4. 公平性LinkedBlockingQueue支持公平性设置(通过构造函数中的fair参数指定),当设置为true时,线程将按照它们被添加到队列的顺序(即FIFO)来获取访问权,这有助于减少饥饿现象。但需要注意的是,公平性通常会降低吞吐量。

  5. 基于链表实现LinkedBlockingQueue内部使用链表结构来存储元素,这使得它在进行插入和删除操作时具有较高的效率,尤其是在队列的头部和尾部进行操作时。

底层结构

LinkedBlockingQueue的底层结构主要包括以下几个部分:

  • 链表节点:每个节点都包含元素值、指向前一个节点的引用以及指向下一个节点的引用,从而构成了一个双向链表。
  • 锁和条件变量LinkedBlockingQueue使用了两把锁(putLocktakeLock)来控制并发访问,分别用于控制插入和删除操作。同时,它还使用了两个条件变量(notFullnotEmpty)来实现线程的阻塞和唤醒。
  • 队列头部和尾部:分别通过headlast指针来指示队列的头部和尾部元素。
  • 计数器:使用一个计数器(count)来记录队列中元素的数量,以便快速判断队列是否为空或已满。

常用用法

LinkedBlockingQueue提供了丰富的操作方法来支持队列的入队、出队以及查询等操作。以下是一些常用的方法:

  1. 入队操作

    • boolean add(E e):向队列尾部添加一个元素,如果队列已满,则抛出IllegalStateException异常。
    • boolean offer(E e):向队列尾部添加一个元素,如果队列已满,则返回false
    • void put(E e) throws InterruptedException:向队列尾部添加一个元素,如果队列已满,则线程被阻塞直到队列有空间可用。
  2. 出队操作

    • E remove():移除并返回队列头部的元素,如果队列为空,则抛出NoSuchElementException
    • E poll():移除并返回队列头部的元素,如果队列为空,则返回null
    • E take() throws InterruptedException:移除并返回队列头部的元素,如果队列为空,则线程被阻塞直到队列有元素可用。
  3. 其他操作

    • int size():返回队列中元素的个数。
    • boolean isEmpty():如果队列为空,则返回true
    • void clear():清空队列中的所有元素。
    • E peek():返回队列头部的元素,但不移除。

这些操作方法使得LinkedBlockingQueue在多线程环境下能够高效地进行数据的存储和传递,特别适合于实现生产者-消费者模式。

PriorityBlockingQueue

Java中的PriorityBlockingQueue是一个支持优先级的无界阻塞队列,它基于优先级堆(通常是二叉堆)实现,确保了队列中元素的有序性。

特点

  1. 优先级排序:队列中的元素会根据其自然排序(如果元素实现了Comparable接口)或者通过构造函数传入的Comparator进行排序。这意味着每次从队列中取出元素时,都是取出当前优先级最高的元素。

  2. 无界但受系统资源限制:虽然PriorityBlockingQueue被设计为无界队列,但实际上它的容量受到系统可用内存的限制。当元素不断被添加到队列中,且队列中的元素数量超过系统所能处理的范围时,可能会导致内存溢出。

  3. 线程安全:作为BlockingQueue接口的实现,PriorityBlockingQueue提供了线程安全的队列操作,允许多个线程同时访问队列而无需进行外部同步。

  4. 阻塞特性:虽然PriorityBlockingQueue是无界的,但在某些场景下(如使用take()或带超时的poll()方法时),如果队列为空,则尝试从队列中取元素的线程会被阻塞,直到队列中有元素可用。

  5. 灵活性:允许在队列实例化时指定初始容量和比较器,以及在不指定初始容量时使用默认容量(通常为11)。

底层结构

PriorityBlockingQueue的底层结构主要包括以下几个部分:

  • 二叉堆:使用二叉堆(通常是最小堆)来存储队列中的元素,确保每次出队的都是优先级最高的元素。二叉堆是一种特殊的完全二叉树,其中每个父节点的值都小于或等于其子节点的值(在最小堆中)。

  • 动态扩容:由于PriorityBlockingQueue是无界的,当元素数量超过当前数组容量时,它会自动进行扩容操作。扩容操作会创建一个新的、更大的数组,并将旧数组中的元素复制到新数组中。为了避免在扩容过程中发生并发问题,扩容操作会先释放锁,然后通过无锁化的CAS(Compare-And-Swap)操作来确保只有一个线程可以成功扩容。

  • 锁和条件变量:使用ReentrantLock作为内部锁来控制对队列的并发访问,并通过Condition(如notEmpty)来实现线程的阻塞和唤醒。

常用用法

PriorityBlockingQueue提供了多种方法来支持队列的入队、出队以及查询等操作。以下是一些常用的方法:

  1. 入队操作

    • boolean add(E e):将指定元素插入此队列,如果此队列已满(实际上对于PriorityBlockingQueue来说,这几乎不可能发生),则抛出IllegalStateException。但请注意,由于PriorityBlockingQueue是无界的,所以这个方法几乎不会抛出异常。
    • boolean offer(E e):将指定元素插入此队列,如果此队列有空间(即未超过系统内存限制),则返回true
    • void put(E e):将指定元素插入此队列,如果队列满(尽管实际上不太可能),则等待可用的空间。但由于PriorityBlockingQueue是无界的,所以这个方法通常不会阻塞。
  2. 出队操作

    • E remove():检索并移除此队列的头部(即优先级最高的元素),如果此队列为空,则抛出NoSuchElementException
    • E poll():检索并移除此队列的头部,如果此队列为空,则返回null
    • E take():检索并移除此队列的头部,在元素变得可用之前,其他线程会在此处阻塞。
  3. 其他操作

    • int size():返回队列中的元素数量。
    • boolean isEmpty():如果队列为空,则返回true
    • Iterator<E> iterator():返回队列中元素的迭代器,但需要注意的是,迭代器提供的是弱一致性的视图,它反映的是迭代器创建时队列的状态,而不保证反映队列的当前状态。

综上所述,PriorityBlockingQueue是一个功能强大且灵活的优先级队列实现,特别适用于需要按优先级处理任务的场景。

Delay Queue

Java中的DelayQueue是一个支持延时获取元素的阻塞队列,它实现了BlockingQueue接口。

特点

  1. 延时性:队列中的元素必须实现Delayed接口,该接口要求元素提供剩余延迟时间(通过getDelay方法)。只有当元素的剩余延迟时间为零或负数时,该元素才能从队列中取出。

  2. 无界性DelayQueue是一个无界队列,这意味着它可以无限期地存储元素,直到系统内存耗尽。

  3. 有序性:队列中的元素按照它们的延迟时间进行排序,延迟时间最短(即最早到期)的元素会被放在队列的头部。

  4. 阻塞性:当队列为空时,尝试从队列中取出元素的线程会被阻塞,直到有元素到期。

  5. 线程安全DelayQueue是线程安全的,允许多个线程同时访问队列而无需进行外部同步。

底层结构

DelayQueue的底层结构主要基于PriorityQueue实现,但添加了一些额外的机制来支持延时和阻塞功能。具体来说,它包含以下几个关键部分:

  • PriorityQueue:用于存储队列中的元素,并根据元素的延迟时间进行排序。PriorityQueue内部实现了一个小顶堆,以确保每次都能快速找到延迟时间最短的元素。

  • ReentrantLock:用于保证队列操作的线程安全。所有对队列的修改操作(如入队、出队)都需要先获取这个锁。

  • ConditionDelayQueue使用Condition对象(通过ReentrantLocknewCondition方法创建)来实现线程的阻塞和唤醒。当队列为空或没有到期的元素时,尝试从队列中取出元素的线程会在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是一个特殊的阻塞队列,它的主要特点是内部没有容量,即每个插入操作必须等待另一个线程的删除操作,反之亦然。

特点

  1. 零容量SynchronousQueue的容量为0,这意味着它不能存储任何元素。插入操作和删除操作是紧密耦合的,即生产者线程必须等待消费者线程来取走元素,反之亦然。

  2. 直接传递:队列中的元素是直接从生产者线程传递给消费者线程的,没有任何中间存储。这种机制适用于需要立即传递数据的情况。

  3. 支持公平性SynchronousQueue提供了两种构造方法,一种是默认的非公平模式,另一种是公平模式。在公平模式下,等待时间最长的线程会优先得到插入或删除元素的机会。

  4. 线程间协作SynchronousQueue提供了一种线程间的协作机制,使得线程之间可以直接进行数据交换,而无需通过中间队列来缓冲。

底层结构

SynchronousQueue的底层实现主要依赖于内部的一个Transferer接口,该接口有两个具体的实现类:TransferStack(基于栈实现,用于非公平模式)和TransferQueue(基于队列实现,用于公平模式)。这些实现类通过维护一系列的内部节点(如请求节点和数据节点)来管理线程的插入和删除操作。

  1. TransferStack:基于栈的非公平实现。当线程尝试插入或删除元素时,它会被包装成一个节点并压入栈中。栈顶元素代表当前正在等待的线程。

  2. TransferQueue:基于队列的公平实现。与TransferStack类似,但它在内部维护了一个队列来管理等待的线程,确保等待时间最长的线程能够优先得到服务。

常用用法

SynchronousQueue常用于需要立即进行线程间数据交换的场景,如生产者-消费者模型中的高并发通信。以下是一些常用方法:

  1. put(E e):将指定元素插入队列中。如果队列为空(即没有等待的线程来取走元素),则当前线程会被阻塞,直到有另一个线程来取走元素。

  2. take():从队列中取出一个元素。如果队列为空(即没有元素可取),则当前线程会被阻塞,直到有另一个线程来插入元素。

  3. offer(E e, long timeout, TimeUnit unit):尝试将元素插入队列中,等待指定的时间。如果在等待时间内有线程来取走元素,则插入成功并返回true;否则,在超时后返回false

  4. poll(long timeout, TimeUnit unit):尝试从队列中取出一个元素,等待指定的时间。如果在等待时间内有元素可取,则返回该元素;否则,在超时后返回null

需要注意的是,由于SynchronousQueue的容量为0,因此它不支持如peekelement等查看但不移除元素的操作,这些操作在SynchronousQueue中是没有意义的。

SynchronousQueue在Java并发编程中有广泛的应用,如线程池中的newCachedThreadPool就使用了SynchronousQueue来实现任务的快速调度和处理。此外,它还可以用于实现限流控制、异步任务结果传递等场景。

LinkedTransferQueue

Java中的LinkedTransferQueue是一个基于链表的无界阻塞队列,它实现了BlockingQueueTransferQueue接口,具有高效、低延迟以及直接的生产者-消费者交互等特点。

特点

  1. 无界性LinkedTransferQueue是一个无界队列,它允许任意数量的元素被添加到队列中,而不用担心队列溢出的问题。

  2. 高效性:它支持高效的线程间数据传递,特别适合于需要高并发和低延迟的场景。

  3. 直接传递LinkedTransferQueue提供了一种机制,使得生产者可以直接将元素传输给等待的消费者,而不需要将元素存储在队列中。这减少了不必要的上下文切换和内存使用。

  4. 避免无效通知:在某些其他阻塞队列中,线程可能会由于操作系统或JVM的原因而意外地提前唤醒(虚假唤醒)。LinkedTransferQueue使用自旋等优化技术来减少这种无效通知,从而提高效率。

  5. 混合支持非阻塞和阻塞操作:除了基本的插入(offer)、移除(poll)和检查(peek)等操作外,还提供了transfertryTransfer等额外的方法,这些方法允许生产者和消费者之间进行更灵活的交互。

底层结构

LinkedTransferQueue的底层结构是由单链表组成的双重队列结构,类似于SynchronousQueue的公平模式。它使用两个指针(head和tail)来分别指向队列的头部和尾部,并通过CAS(Compare-And-Swap)原子操作来更新这两个指针。每个节点(Node)都包含数据项(item)、后继节点(next)、等待线程(waiter)等信息,并通过volatile关键字保证多线程环境下的可见性。

常用用法

LinkedTransferQueue提供了丰富的方法来支持生产者和消费者之间的交互,以下是一些常用方法:

  1. transfer(E e):如果当前存在一个正在等待获取的消费者线程,则立即将元素e传输给该消费者;否则,将元素e插入队列尾部,并等待直到有消费者线程取走该元素。这是一个阻塞操作。

  2. tryTransfer(E e):如果当前存在一个正在等待获取的消费者线程,则立即将元素e传输给该消费者;如果不存在,则返回false,并且不将元素e插入队列。这是一个非阻塞操作。

  3. tryTransfer(E e, long timeout, TimeUnit unit):如果当前存在一个正在等待获取的消费者线程,则立即将元素e传输给该消费者;如果不存在,则将元素e插入队列尾部,并等待指定的时间。如果在指定时间内元素e被消费者线程取走,则返回true;否则,返回false,并且从队列中移除元素e。

  4. poll():移除并返回队列头部的元素,如果队列为空,则返回null。这是一个非阻塞操作。

  5. take():移除并返回队列头部的元素,如果队列为空,则阻塞直到有元素可用。

  6. peek():返回队列头部的元素,但不移除。如果队列为空,则返回null

  7. size():由于队列的异步特性,检测当前队列的元素个数需要逐一迭代,可能会得到一个不太准确的结果,尤其是在遍历时有可能队列发生更改。因此,这个方法的使用需要谨慎。

  8. hasWaitingConsumer():判断是否存在正在等待获取元素的消费者线程。

LinkedTransferQueue的这些方法使得它特别适合于那些需要高效、低延迟以及直接生产者-消费者交互的并发场景,如工作窃取算法或任务传递系统等。

LinkedBlockingQueue

Java中的LinkedBlockingQueue是一个线程安全的阻塞队列,它实现了BlockingQueue接口,并基于链表结构实现。

特点

  1. 线程安全LinkedBlockingQueue内部使用了锁机制来保证多线程环境下的线程安全,因此可以被多个线程同时使用而不需要额外的同步措施。
  2. 阻塞特性:当队列满时,插入操作(如put方法)会被阻塞,直到队列中有空间可用;当队列空时,移除操作(如take方法)会被阻塞,直到队列中有元素可供移除。
  3. 可选容量限制LinkedBlockingQueue可以在初始化时指定最大容量,如果未指定,则默认容量为Integer.MAX_VALUE,即无界队列。
  4. 公平性LinkedBlockingQueue可以选择是否按照FIFO(先进先出)原则对等待在队列中的线程进行调度。如果设置为公平模式(通过设置构造函数中的fair参数为true),则线程会按照请求的顺序被唤醒,但请注意,公平模式可能会降低吞吐量。
  5. 基于链表:内部使用链表来存储元素,这使得它在插入和删除元素时具有较高的效率。

底层结构

LinkedBlockingQueue的底层结构主要由以下几个部分组成:

  • 链表节点(Node):每个节点包含元素数据、前驱节点引用和后继节点引用。
  • 头节点(head)和尾节点(last):分别指向队列的首部和尾部,用于快速定位队列的首尾元素。
  • 锁机制:通常使用两把锁,一把用于控制插入操作(putLock),另一把用于控制删除操作(takeLock),以提高并发性能。此外,还可能使用条件变量(notEmpty和notFull)来实现线程的阻塞和唤醒。

常用用法

LinkedBlockingQueue提供了多种方法来支持队列的基本操作以及阻塞操作:

  1. 插入元素:
    • add(E e):向队列尾部添加一个元素,如果队列已满,则抛出IllegalStateException异常。
    • offer(E e):向队列尾部添加一个元素,如果队列已满,则返回false
    • put(E e):向队列尾部添加一个元素,如果队列已满,则线程被阻塞直到队列有空间可用。
  2. 移除元素:
    • poll():移除并返回队列头部的元素,如果队列为空,则返回null
    • take():移除并返回队列头部的元素,如果队列为空,则线程被阻塞直到有元素可用。
  3. 查看元素:
    • peek():返回队列头部的元素,但不移除。如果队列为空,则返回null
  4. 其他操作:
    • size():返回队列中元素的个数。
    • remainingCapacity():返回队列的剩余容量。
    • isEmpty():判断队列是否为空。
    • clear():清空队列中的所有元素。
posted @   BingBing爱化学-04044  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示