java中常用的阻塞队列与非阻塞队列

队列概述以及常用方法

队列是一个有序列表,可以用数组或者链表实现,且遵循先进先出的原则,在此基础上还分为阻塞、非阻塞、优先队列等。
java中只要是队列都实现了Queue接口,队列接口定义的方法如下:

方法名描述注意
add(E)增加一个元索添加一个元素,添加成功返回true;如果队列空间已满,则抛出异常
offer(E)增加一个元索添加一个元素,添加成功返回true;如果队列空间已满,返回false
remove()返回并删除队首元素如果队列为空,则抛出异常
poll()返回并删除队首元素如果队列为空,则返回null
element()返回队首元素,但不移除队列为空则抛出异常
peek()返回队首元素,但不移除如果队列为空,则返回null

1 非阻塞队列

1 ConcurrentLinkedQueue(线程安全)

单向链表结构的无界并发队列, 非阻塞队列,由CAS实现线程安全

是否有界:无界
是否线程安全:是
性能:高

2 ConcurrentLinkedDeque(线程安全)

双向链表结构的无界并发队列, 非阻塞队列,由CAS实现线程安全,相较于ConcurrentLinkedQueue,ConcurrentLinkedDeque在某些场景下提供了更加灵活的操作,比如可以从两端进行元素的插入和删除操作。

是否有界:无界
是否线程安全:是
性能:略低于ConcurrentLinkedQueue

3 PriorityQueue(线程不安全)

PriorityQueue是Java中的一个基于优先级堆的队列,基于数组实现,它可以按照元素的优先级进行排序,并且在插入和删除元素时保持有序状态。具体来说,PriorityQueue中的元素必须实现Comparable接口或者通过构造函数提供一个Comparator对象,以便进行元素的比较和排序。

是否有界:无界
是否线程安全:否(如需线程安全可以使用PriorityBlockingQueue)
性能:比普通队列略低(插入和获取操作都需要进行堆调整和排序操作)

PriorityQueue的实现基于数组,它可以自动扩容,但是它的容量大小是有限制的。在插入和删除元素时,PriorityQueue会根据元素的优先级重新调整堆的结构,保证堆顶元素一定是优先级最高的元素。

2 阻塞队列(线程安全)

阻塞队列BlockingQueue继承了 Queue 接口,是队列的一种,阻塞队列是线程安全的,阻塞队列在队列基础上加了两个重要的接口put() take()

方法\处理方式抛出异常返回true或false一直阻塞超时退出
插入方法add(e)offer(e)put(e)offer(e,time,unit)
移除方法remove()poll()take()poll(time,unit)
检查方法element()peek()不可用不可用

注意:
在ThreadPoolExecutor中就使用的阻塞队列来实现线程的不结束,以达到线程复用。

常见阻塞队列,juc中常用的阻塞队列如下:

1. ArrayBlockingQueue

基于数组结构实现的一个有界阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。

它使用了同步机制来保证多个线程对队列的访问不会发生竞态条件。具体来说,ArrayBlockingQueue使用ReentrantLockCondition来保证线程安全和队列的阻塞和唤醒。

是否有界:有
是否线程安全:是
性能:高

2. LinkedBlockingQueue

基于链表结构实现的一个有界阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE

是否有界:无
是否线程安全:是
性能:性能低于ArrayBlockingQueue

LinkedBlockingQueue的实现使用了两个锁,一个用于生产者线程的访问,另一个用于消费者线程的访问。这样可以避免在高并发场景下产生竞态条件,保证了线程安全性。

LinkedBlockingQueue的性能比ArrayBlockingQueue略低,因为它使用链表而不是数组来存储元素。

但是,由于LinkedBlockingQueue是无界队列,因此它在需要动态调整容量时更加灵活,可以根据实际情况自动扩容或缩容。

总的来说,LinkedBlockingQueue是一个非常实用的数据结构,在生产者-消费者模型、线程池等多线程场景中被广泛使用。

3. SynchronousQueue

SynchronousQueue内部并不存储任何元素,它只是在等待其他线程将元素插入或删除队列时才会阻塞,每一个put操作必须等待一个take操作,否则不能继续添加元素

是否有界:有
是否线程安全:是
性能:-

具体来说,当一个线程调用SynchronousQueue的put()方法时,如果没有其他线程在等待从队列中取出元素,那么当前线程将会阻塞,直到有其他线程调用take()方法来取出这个元素。

同样地,当一个线程调用take()方法时,如果没有其他线程在等待向队列中插入元素,那么当前线程也会阻塞,直到有其他线程调用put()方法来插入元素。

SynchronousQueue可以用于一些特殊的场景,例如在生产者和消费者之间进行高效的交互。在这种情况下,生产者线程将元素直接交给消费者线程处理,而不需要通过中间缓存。另外,SynchronousQueue还可以用于实现一些并发算法和数据结构,例如公平的交换器(Fair Exchanger)。

需要注意的是,SynchronousQueue不允许null元素,如果试图插入null元素,将会抛出NullPointerException。

4. LinkedTransferQueue

基于链表结构实现的一个无界阻塞队列,是 SynchronousQueue LinkedBlockingQueue 的合体,它使用了CAS(Compare and Swap)操作来保证并发安全性,与其他阻塞队列不同,LinkedTransferQueue内部使用了多个节点来实现队列,每个节点包含一个元素以及指向下一个节点的指针,这种方式可以避免在并发场景下使用锁导致的性能瓶颈,性能比 LinkedBlockingQueue 更高(没有锁操作),比 SynchronousQueue能存储更多的元素

是否有界:无
是否线程安全:是
性能:高

TransferQueue接口相较普通的阻塞队列,增加了这么几个方法:

public interface TransferQueue<E> extends BlockingQueue<E> {
    // 如果可能,立即将元素转移给等待的消费者。 
    // 更确切地说,如果存在消费者已经等待接收它(在 take 或 timed poll(long,TimeUnit)poll)中,则立即传送指定的元素,否则返回 false。
    boolean tryTransfer(E e);

    // 将元素转移给消费者,如果需要的话等待。 
    // 更准确地说,如果存在一个消费者已经等待接收它(在 take 或timed poll(long,TimeUnit)poll)中,则立即传送指定的元素,否则等待直到元素由消费者接收。
    void transfer(E e) throws InterruptedException;

    // 上面方法的基础上设置超时时间
    boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException;

    // 如果至少有一位消费者在等待,则返回 true
    boolean hasWaitingConsumer();

    // 返回等待消费者人数的估计值
    int getWaitingConsumerCount();
}

但是在使用LinkedTransferQueue时,需要注意一些细节:

  • LinkedTransferQueue不支持null元素,如果插入null元素将会抛出NullPointerException异常。

  • 在使用transfer()方法时,如果队列已满或为空,调用transfer()方法的线程将会被阻塞,直到另一个线程将元素插入或取走。因此,在使用transfer()方法时需要注意线程的阻塞问题,避免出现死锁或线程饥饿的情况。

  • LinkedTransferQueue的迭代器只能用于遍历当前队列中的元素,无法保证在迭代期间队列中的元素不被修改或删除。

5. LinkedBlockingDeque

基于双向链表结构实现的一个双向阻塞队列,它可以同时从队列的两端插入和删除元素,因此支持队列的操作。

是否有界:无界,但是可以设置队列上限
是否线程安全:是
性能:高

使用LinkedBlockingDeque时需要注意以下几点:

  • LinkedBlockingDeque不支持null元素,如果插入null元素将会抛出NullPointerException异常。

  • LinkedBlockingDeque的阻塞特性可能会导致线程饥饿,即某些线程一直无法获得访问队列的机会。为避免这种情况,可以在创建LinkedBlockingDeque时指定容量,或者使用putFirst()putLast()takeFirst()takeLast()等非阻塞操作。

6. PriorityBlockingQueue

基于优先级的阻塞队列,它使用数组实现,并且具有无界限的容量,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。

PriorityBlockingQueue的注意事项如下:

  • PriorityBlockingQueue允许插入null元素,但是不允许插入不可比较的元素,如果插入不可比较的元素,将会抛出ClassCastException异常。

  • PriorityBlockingQueue的迭代顺序并不是按照元素的优先级顺序排列的。虽然PriorityBlockingQueue能够保证每次取出的元素都是优先级最高的元素,但是在迭代PriorityBlockingQueue时,它的顺序是无法保证的。

  • PriorityBlockingQueue在插入元素时,会根据元素的优先级对元素进行排序。因此,如果队列中的元素的优先级发生了变化,需要调用队列中的元素的offer、add、put方法重新排序。

  • PriorityBlockingQueue不支持remove(Object)操作,因为它无法快速地找到并删除指定元素。如果需要删除指定元素,可以先将PriorityBlockingQueue中的元素复制到另一个集合中,然后再从新集合中删除指定元素,最后将新集合中的元素重新添加到PriorityBlockingQueue中。

  • PriorityBlockingQueue在迭代、插入和删除元素时,都使用了ReentrantLock来保证线程安全。因此,在使用PriorityBlockingQueue时需要注意避免死锁和饥饿等问题。

简单示例:

import java.util.concurrent.PriorityBlockingQueue;

public class Task implements Comparable<Task> {
    private String name;
    private int priority;

    public Task(String name, int priority) {
        this.name = name;
        this.priority = priority;
    }

    public String getName() {
        return name;
    }

    public int getPriority() {
        return priority;
    }

    @Override
    public int compareTo(Task o) {
        return Integer.compare(o.priority, this.priority);
    }
}

public class PriorityBlockingQueueExample {
    public static void main(String[] args) {
        // 创建一个PriorityBlockingQueue对象
        PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<Task>();
        // 添加任务到队列中
        queue.offer(new Task("Task1", 3));
        queue.offer(new Task("Task2", 2));
        queue.offer(new Task("Task3", 1));
        // 取出队列中的任务
        while (!queue.isEmpty()) {
            Task task = queue.poll();
            System.out.println("Execute task " + task.getName() + " with priority " + task.getPriority());
        }
    }
}
输出结果:
Execute task Task1 with priority 3
Execute task Task2 with priority 2
Execute task Task3 with priority 1

7. DelayQueue

是一种延时阻塞队列,它可以存储实现了Delayed接口的元素,其中每个元素都有一个过期时间,即当元素的过期时间到达时,元素会被取出

是否有界:无界的,但是在实际应用中,我们可以通过指定初始容量来控制队列的大小,以避免内存溢出等问题。
是否线程安全:是
性能:性能要比普通的阻塞队列略低(插入和获取操作都需要进行堆调整和排序操作)

DelayQueue内部使用PriorityQueue实现,元素会按照过期时间排序,即过期时间最短的元素排在队列的头部。DelayQueue的插入操作是非阻塞的,但是取出操作是阻塞的,即当队列中没有过期元素时,取出操作会一直被阻塞,直到队列中有过期元素被插入。

使用DelayQueue时需要注意以下几点:

  • DelayQueue的元素必须实现Delayed接口,并重写getDelay()compareTo()方法,其中getDelay()方法返回元素的过期时间距当前时间的剩余时间(单位为毫秒),compareTo()方法用于比较元素的过期时间。

  • DelayQueue内部使用ReentrantLockCondition实现线程同步和阻塞操作,因此是线程安全的。

  • DelayQueue的取出操作可能会被阻塞,如果需要在队列为空时立即返回,可以使用poll()方法而不是take()方法。

  • DelayQueue在理论上是无界的,因为它使用PriorityQueue来存储元素,PriorityQueue的长度是没有限制的。但是,如果你想控制DelayQueue的大小,你可以在创建DelayQueue实例时指定一个初始容量。

    DelayQueue<DelayedTask> queue = new DelayQueue<DelayedTask>(new ArrayList<DelayedTask>(capacity));
    

下面是一个简单的使用DelayQueue的示例代码,在取出元素时,队列会自动按照元素的过期时间进行排序,并等待元素过期后再取出。注意,在实际使用中,需要根据具体的业务场景来设置元素的过期时间和比较方式:

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class DelayQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue<DelayElement> queue = new DelayQueue<>();

        // 添加元素到队列
        queue.add(new DelayElement("A", 3000));
        queue.add(new DelayElement("B", 2000));
        queue.add(new DelayElement("C", 1000));

        // 取出元素
        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
    }
}

class DelayElement implements Delayed {
    private String name;
    private long expireTime;

    public DelayElement(String name, long delayTime) {
        this.name = name;
        this.expireTime = System.currentTimeMillis() + delayTime;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return expireTime - System.currentTimeMillis();
    }

    @Override
    public int compareTo(Delayed o) {
        return Long.compare(this.expireTime, ((DelayElement) o).expireTime);
    }

    @Override
    public String toString() {
        return "DelayElement{" +
                "name='" + name + '\'' +
                '}';
    }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

L-960

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值