资讯专栏INFORMATION COLUMN

Java 队列

Pocher / 2509人阅读

摘要:队列中有元素时,就说明有过期了,线程继续执行,然后元素出队,根据相应的移除缓存。所以严格来说,虽然实现了队列接口,但是它的目的却并不是队列,而是将生产者消费者线程配对。转移队列链式转移队列。

引言

本周在编写短信验证码频率限制切面的时候,经潘老师给的实现思路,使用队列进行实现。

看了看java.util包下的Queue接口,发现还从来没用过呢!

Collection集合类接口,由它派生出ListSetQueueMap属于另一个独立的接口,和Collection没有继承关系。

ListSetMap我们用的都是已经相当熟练了,今天,我们就来学习这个队列Queue

探索

队列与栈都是数据结构的基础话题,队列:先进先出;栈:后进先出。

方法

Queue接口中声明了六个方法,分成三对来使用。

入队操作

方法 特点 建议
add 入队失败抛出异常
offer 入队失败返回false 推荐

出队操作

方法 特点 建议
remove 出队失败抛出异常
poll 出队失败返回null 推荐

取队头操作

方法 特点 建议
element 队列为空时抛出异常
peek 队列为空时返回null 推荐
PriorityQueue

java.util包中,除抽象类外,直接实现Queue接口的只有PriorityQueue优先级队列。

优先级队列比普通的队列要高级,普通的队列如果是先进的肯定是在队头的,而优先级队列根据优先级判断当前队头元素是什么。很适合实现操作系统中的按优先级实现进程调度。

如果需要使用优先级队列进行排序时,需要传入比较器。

该队列使用数组实现,线程不安全。

Deque

java.util包中,Deque接口继承Queue接口。

Dequedouble-ended queue,双端队列。

双端队列,相比普通队列就是可操作两端,有两个队头,也有两个队尾。

所以再去看Deque接口中声明的方法,都是两套的。offerFirstofferLastpollFirstpollLast等。

所以说,如果使用双端队列,不仅可以当队列用,也可以当栈用,因为可以自己控制出的是队头还是队尾。

Deque有两个实现类:ArrayDequeLinkedList

原来LinkedList不仅实现了List接口,还实现了Deque接口。

两者的区别显而易见,一个是数组方式实现的,一个是链表的方式实现的。

BlockingQueue

这些都是java.util包下的,都是线程不安全的实现,JDK所有线程安全的队列实现都在java.util.concurrent包下,也就是阻塞队列BlockingQueue

concurrent包下,自然是做了线程安全处理的了,在多线程环境下操作队列需要使用。

生产者消费者

与阻塞队列最密切的就是生产者消费者模型了,我们一起来探讨一下。

生产者消费者模型,最初出现在操作系统中,多进程/多线程进行协作,完成同一任务,必然需要相互合作与相互制约。

举一个符合实际的例子,我想喝可乐。

可口可乐公司就是生产者,用于生产商品。

超市就相当于缓冲区,用于存储生产者生产出来的可乐,公司生产出可乐,然后放到超市里卖。

我就是消费者,去超市买可乐(消费过程)。

所以就会有一个同步的问题:

假设场景:超市能容量100瓶可乐。

所以,消费者去购买的前提是:超市内有可乐,要不去了也买不着。

生产者生产的前提是:超市内有空余位置,要不生产了往哪送呢?

类比到程序设计中,就是进程或线程之间的相互制约,也就是所谓的同步!

线程类比

一图胜千言,我就不赘述了。

消费者线程想去找缓冲区要数据,先判断缓冲区内有没有数据,如果没有,消费者就拿不到,这个线程就等待,直到:缓冲区内有数据。如果有,就从缓冲区将数据拿走。

生产者线程要去生产数据,先判断缓冲区内有没有空余位置,如果没有,生产者就等待,直到:缓冲区内有空位,如果有,就生产数据,放入缓冲区。

阻塞队列

阻塞队列正适合生产者消费者模型,当队列满时,入队操作就会被阻塞,当队列空时,出队操作就会被阻塞。

入队出队的offer方法和poll方法与原队列接口的方法相比,多了时间的参数。当发生阻塞时,如果超过了设置的时间,线程就会退出,毕竟如果最坏的情况,一直不满足条件,也不能一直阻塞下去。

boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
E poll(long timeout, TimeUnit unit) throws InterruptedException;
实现类

ArrayBlockingQueue:数组实现的阻塞队列。

LinkedBlockingQueue:链表实现的阻塞队列。

PriorityBlockingQueue:优先级阻塞队列。

双向阻塞队列

这个简单,就是同时实现了BlockingQueueDeque接口。

java.util.concurrent包下只有一个双向阻塞队列的实现:LinkedBlockingDeque

延时队列

延时队列:DelayQueue,看这个类名,无疑了,此队列定与时间有关。

当一个元素入队时,它并不是马上进入队列,而是根据设定的时间延时之后再入队。

假设offer一个元素,设置时间为10s,在10s内访问队列,是访问不到元素的。

在延时之后,也就是10s之后,再去访问,该元素才在队列中。

使用场景

相关使用场景就是定时缓存。

HashMapDelayQueue配合使用。用DelayQueue来存储缓存的key,如果队列中有元素,表示该key就已经过期。

然后再建一个线程去清理缓存,执行到poll方法时,使用不传时间的方法,如果队列为空,该线程就一直阻塞在这,不往下走。

队列中有元素时,就说明有key过期了,线程继续执行,然后元素出队,根据相应的key移除缓存。

细节

延时队列中存储的元素需要实现Delayed接口。

public interface Delayed extends Comparable {
    long getDelay(TimeUnit unit);
}

getDelay方法返回剩余的延时时间,如果返回值大于0,表示还未到入队时间。

同步队列

SynchronousQueue:同步队列。

最好的解释自然是官方文档:A BlockingQueue in which each insert operation must wait for a corresponding remove operation by another thread, and vice versa.

这是一个阻塞队列,它的特点是在执行插入操作时必须等待另一个线程的移除操作。什么意思呢?

通俗的来说就是买可乐不需要去超市了,我(消费者)直接和厂家(生产者)购买。

所以,生产者和消费者同时存在时,这个交易才能执行,两方达成约定后,生产者生产可乐,卖给消费者。缺少任何一方另一方都会被阻塞,条件满足时会唤醒对方继续执行,这就是所谓的同步。

代码层面讲就是:puttake方法都被调用的时候,两者才开始执行,并完成了数据的传递。

所以严格来说,虽然SynchronousQueue实现了队列接口,但是它的目的却并不是队列,而是将生产者消费者线程配对。

转移队列

LinkedTransferQueue:链式转移队列。虽然放在了最后,但是查阅相关文档发现,实际的生产环境中,这个队列最常用。

怎么转移的呢?

消费者找队列拿数据,如果没有数据可用,就设置一个标志位,表示我这里期待着一个数据,然后消费者就开始等。

等着等着,直到生产者来了,判断,如果有等着的,就直接把数据给它,实现了数据转移。如果没有呢?就去执行数据入队相关的操作。

总结

点开了阻塞队列的源码,发现线程安全是使用锁实现的。

再看看面试问的东西:乐观锁、悲观锁、自旋锁、偏向锁、公平锁,这都是写啥东西呀?

吾生也有涯,而Java无涯。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74322.html

相关文章

  • Java知识点总结(Java容器-Queue)

    摘要:知识点总结容器知识点总结容器接口与是在同一级别,都是继承了接口。另一种队列则是双端队列,支持在头尾两端插入和移除元素,主要包括。一个由链表结构组成的无界阻塞队列。是一个阻塞的线程安全的队列,底层实现也是使用链式结构。 Java知识点总结(Java容器-Queue) @(Java知识点总结)[Java, Java容器] Queue Queue接口与List、Set是在同一级别,都是继承了...

    hedzr 评论0 收藏0
  • [Java并发-6]“管程”-java管程初探

    摘要:语言在之前,提供的唯一的并发原语就是管程,而且之后提供的并发包,也是以管程技术为基础的。但是管程更容易使用,所以选择了管程。线程进入条件变量的等待队列后,是允许其他线程进入管程的。并发编程里两大核心问题互斥和同步,都可以由管程来帮你解决。 并发编程这个技术领域已经发展了半个世纪了。有没有一种核心技术可以很方便地解决我们的并发问题呢?这个问题, 我会选择 Monitor(管程)技术。Ja...

    Steve_Wang_ 评论0 收藏0
  • 什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?

    摘要:什么是阻塞队列阻塞队列是一个在队列基础上又支持了两个附加操作的队列。阻塞队列的应用场景阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。由链表结构组成的无界阻塞队列。 什么是阻塞队列? 阻塞队列是一个在队列基础上又支持了两个附加操作的队列。 2个附加操作: 支持阻塞的插入方法:队列满时,队列会阻塞插入元素的线程,直到队列不满。 支持阻塞的...

    jemygraw 评论0 收藏0
  • java 队列

    摘要:是基于链接节点的线程安全的队列。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。队列内部仅允许容纳一个元素。该队列的头部是延迟期满后保存时间最长的元素。 队列简述 Queue: 基本上,一个队列就是一个先入先出(FIFO)的数据结构Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Deque接 口。...

    goji 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<