Java综合知识——JAVA中几种重要的队列、使用场景、实现方式与特点

本文详细介绍了Java中队列的原理、接口及其实现,包括Queue、Deque、BlockingQueue、PriorityQueue和DelayQueue的区别和应用场景,如线程池、订单处理、优先级调度和延迟操作。
摘要由CSDN通过智能技术生成

1.1.2 JAVA中几种重要的队列、使用场景、实现方式与特点

一、什么是队列?

队列,实质是一种存储数据的结构,通常用链表或者数组实现。一般具备FIFO(先进先出)的特性,当然也有双端队列(Deque),优先队列等。主要的操作:入队(Enqueue)和出队(Dequeue).

img

在Java中, 定义了队列的基本操作,接口类型为 java.util.Queue,接口定义如下所示。Queue 定义了两套队列操作方法:

public interface Queue<E> extends Collection<E> {
    //插入元素,成功返回true,失败抛出异常
    boolean add(E e);
​
    //插入元素,成功返回true,失败返回false或抛出异常 
    boolean offer(E e);
​
    //取出并移除头部元素,空队列抛出异常 
    E remove();
​
    //取出并移除头部元素,空队列返回null 
    E poll();
​
    //取出但不移除头部元素,空队列抛出异常 
    E element();
​
    //取出但不移除头部元素,空队列返回null 
    E peek();
​
}

Queue 作为先进先出队列,只能从头部取元素、插入元素到尾部。

Java 同样定义了双向队列 Deque,可以同时在头部、尾部插入和取出元素,接口定义如下所示,针对头部操作方法为 xxxFirst、针对尾部操作方法为 xxxLast:

public interface Deque<E> extends Queue<E> {
    //插入元素到队列头部,失败抛出异常 
    void addFirst(E e);
​
    //插入元素到队列尾部,失败抛出异常  
    void addLast(E e);
​
    //插入元素到队列头部,失败返回false或抛出异常 
    boolean offerFirst(E e);
​
    //插入元素到队列尾部,失败返回false抛出异常  
    boolean offerLast(E e);
​
    //取出并移除头部元素,空队列抛出异常 
    E removeFirst();
​
    //取出并移除尾部元素,空队列抛出异常 
    E removeLast();
​
    //取出并移除头部元素,空队列返回null
    E pollFirst();
​
    //取出并移除尾部元素,空队列返回null
    E pollLast();
​
    //取出但不移除头部元素,空队列抛出异常
    E getFirst();
​
    //取出但不移除尾部元素,空队列抛出异常
    E getLast();
​
    //取出但不移除头部元素,空队列返回null
    E peekFirst();
​
    //取出但不移除尾部元素,空队列返回null
    E peekLast();
​
    //移除指定头部元素,若不存在队列不变,移除成功返回true 
    boolean removeFirstOccurrence(Object o);
​
    //移除指定尾部元素,若不存在队列不变,移除成功返回true 
    boolean removeLastOccurrence(Object o);
​
    //单向队列方法,参考Queue   
    //栈方法,参考栈
    //集合方法,参考集合定义   
}

不仅如此,Java 并发工具包中定义了阻塞队列 BlockingQueue 和 BlockingDueue。阻塞队列在前面的队列定义基础上增加了以下几个方法,来支持阻塞操作:

  • take:取出并移除元素,如队列为空则一直阻塞直到有元素;

  • put:插入元素,如队列满则一直阻塞直到有空位可以插入元素;

  • 可超时的offer:插入元素并指定超时时间,如队列满等待指定的时间;

  • 可超时的poll:取出并移除元素,如队列空等待指定的时间。

一般情况下 offer() 和 poll() 方法配合使用,put() 和 take() 阻塞方法配合使用,add() 和 remove() 方法会配合使用,程序中常用的是 offer() 和 poll() 方法,因此这两个方法比较友好,不会报错。

二、队列的分类

Java 中的这些队列可以从不同的维度进行分类,例如可以从阻塞和非阻塞进行分类,也可以从有界和无界进行分类,而本文将从队列的功能上进行分类,例如:优先队列、普通队列、双端队列、延迟队列等

file

1、阻塞队列与非阻塞队列
1.1 阻塞队列

阻塞队列(Blocking Queue)提供了可阻塞的 put 和 take 方法,它们与可定时的 offer 和 poll 是等价的。如果队列满了 put 方法会被阻塞等到有空间可用再将元素插入;如果队列是空的,那么 take 方法也会阻塞,直到有元素可用。当队列永远不会被充满时,put 方法和 take 方法就永远不会阻塞。在java包"java.util.concurrent"中,提供六个实现了"BlockingQueue"接口的阻塞队列:

  1. ArrayBlockingQueue 用数组实现的有界阻塞队列;

  2. LinkedBlockingQueue 基于链表实现的有界阻塞队列

  3. PriorityBlockingQueue是一个带优先级的队列,基于堆数据结构的;

  4. DelayQueue是在PriorityQueue基础上实现的,底层也是数组构造方法,是一个存放Delayed 元素的无界阻塞队列;

  5. SynchronousQueue 一个没有容量的队列 ,不会存储数据;

  6. LinkedBlockingDeque 是双向链表实现的双向并发阻塞队列;

1.2非阻塞队列

所有无Blocking Queue的都是非阻塞,并且它不会包含 put 和 take 方法。

2、有界队列和无界队列
2.1 有界队列

是指有固定大小的队列,比如设定了固定大小的 ArrayBlockingQueue,又或者大小为 0 的 SynchronousQueue。

2.2 无界队列

指的是没有设置固定大小的队列,但其实如果没有设置固定大小也是有默认值的,只不过默认值是 Integer.MAX_VALUE。

3、双端队列

Deque是一个双端队列接口,继承自Queue接口,Deque的实现类是LinkedList、ArrayDeque、LinkedBlockingDeque,其中LinkedList是最常用的。

Java双端队列-CSDN博客

4、优先队列

优先队列(PriorityQueue)是一种特殊的队列,它并不是先进先出的,而是优先级高的元素先出队。 优先队列是根据二叉堆实现的。最大堆和最小堆。

5、延迟队列

延迟队列(DelayQueue)是基于优先队列 PriorityQueue 实现的,它可以看作是一种以时间为度量单位的优先的队列,当入队的元素到达指定的延迟时间之后方可出队。

三、队列的使用场景

最典型的就是线程池,不同的线程池都是基于不同的队列来实现多任务等待的。

1.LinkedBlockingQueue使用场景:

在项目的一些核心业务且生产和消费速度相似的场景中:订单完成的邮件/短信提醒。 订单系统中当用户下单成功后,将信息放入ArrayBlockingQueue中,由消息推送系统取出数据进行消息推送提示用户下单成功。如果订单的成交量非常大,那么使用ArrayBlockingQueue就会有一些问题,固定数组很容易被使用完,此时调用的线程会进入阻塞,那么可能无法及时将消息推送出去,所以使用LinkedBlockingQueue比较合适,但是要注意消费速度不能太低,不然很容易内存被使用完。

2.PriorityBlockingQueue使用场景:

在项目上存在优先级的业务:VIP排队购票 用户购票的时候,根据用户不同的等级,优先放到队伍的前面,当存在票源的时候,根据优先级分配

3.DelayQueue使用场景 :

由于是基于优先级队列实现,但是它比较的是时间,我们可以根据需要去倒叙或者正序排列(一般都是倒叙,用于倒计时)。所以适用于:

订单超时取消功能、网站刷题倒计时 用户下订单未支付开始倒计时,超时则释放订单中的资源,如果取消或者完成支付,我们再将队列中的数据移除掉。

4.SynchronousQueue使用场景:

参考线程池newCachedThreadPool()。 如果我们不确定每一个来自生产者请求数量但是需要很快的处理掉,那么配合SynchronousQueue为每个生产者请求分配一个消费线程是最简洁的办法。

cking Queue)提供了可阻塞的 put 和 take 方法,它们与可定时的 offer 和 poll 是等价的。如果队列满了 put 方法会被阻塞等到有空间可用再将元素插入;如果队列是空的,那么 take 方法也会阻塞,直到有元素可用。当队列永远不会被充满时,put 方法和 take 方法就永远不会阻塞。在java包"java.util.concurrent"中,提供六个实现了

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java,线程可以通过以下几种方式实现: 1. 继承Thread类:可以创建一个继承自Thread类的子类,并重写run()方法来定义线程的执行逻辑。然后通过创建该子类的实例来启动线程。 ```java class MyThread extends Thread { public void run() { // 线程执行逻辑 } } // 创建并启动线程 MyThread thread = new MyThread(); thread.start(); ``` 2. 实现Runnable接口:可以创建一个实现了Runnable接口的类,并实现run()方法,然后通过创建该类的实例作为参数来构造Thread对象,并调用start()方法启动线程。 ```java class MyRunnable implements Runnable { public void run() { // 线程执行逻辑 } } // 创建并启动线程 Thread thread = new Thread(new MyRunnable()); thread.start(); ``` 3. 使用Callable和Future:可以创建一个实现了Callable接口的类,并实现call()方法,该方法可以返回一个结果。然后通过创建ExecutorService线程池的实例,调用submit()方法提交Callable任务,并返回一个Future对象,通过调用Future对象的get()方法可以获取任务执行的结果。 ```java import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; class MyCallable implements Callable<String> { public String call() throws Exception { // 线程执行逻辑 return "Result"; } } // 创建线程池 ExecutorService executor = Executors.newFixedThreadPool(1); // 提交任务并获取Future对象 Future<String> future = executor.submit(new MyCallable()); // 获取任务执行结果 String result = future.get(); // 关闭线程池 executor.shutdown(); ``` 这些是Java常用的线程实现方式,每种方式都有其适用的场景特点,可以根据具体需求选择合适的方式实现多线程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值