我用漫画带你学习Queue的接口与实现类

推荐阅读:

一、Queue概要

1. 队列概述

Collection的子接口。表示队列FIFO(First In First Out);队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。

Java 1.8 API :

队列通常(但不一定)以 FIFO(先出先出)的方式对元素排序。例外情况包括优先级队列,这些队列根据提供的比较器对元素进行排序,或者元素的自然排序,以及对元素 LIFO(最后一个先出)排序的 LIFO 队列(或堆栈)。无论使用何种顺序,队列的主管都是该元素,该元素将通过调用remove()poll()删除。在 FIFO 队列中,所有新元素都插入到队列的尾部。其他类型的队列可能使用不同的放置规则。每个实现都必须指定其排序属性。

2. 常用方法:

 这些方法中的每一种都有两种形式:如果操作失败,则抛出一个异常,另一种返回一个特殊值( null或false ,具体取决于操作)。

  • 1.抛出异常
方法 描述
boolean add (E e) 顺序添加一个元素(到达上限后,再添则会抛出异常)
E remove() 获得第一个元素并移除(如果队列没有元素时,则抛出异常)
E element() 获得第一个元素但不移除(如果队列没有元素时,则会抛出异常)
  • 2.返回特殊值:推荐使用
方法 描述
boolean offer(E e) 顺序添加一个元素(到达上限后 ,再添加则会返回fals)
E poll() 获得第一个元素并移除(如果队列没有元素时,则返回null)
E peek() 获得第一元素但不移除(如果队列没有元素时,则返回null)

二、Queue接口的继承接口和实现类

所有已知的实现类
在这里插入图片描述

1. ConcurrentLinkedQueue接口

 基于链接节点的无界线程安全队列。此队列订购元素 FIFO(先出先)。队列的头部是队列中时间最长的元素。队列的尾部是队列中时间最短的元素。新元素插入到队列的尾部,队列检索操作获取队列头处的元素。当许多线程共享对公共集合的访问时,ConcurrentLinkedQueue是适当的选择。与大多数其他并发集合实现一样,此类不允许使用null元素。

  • Java编程中,线程安全、可高效读写的队列。高并发下性能最好的队列。

 一般来说,线程安全都必须加锁,而加锁就不支持高并发,进而无法实现高效率,但是对于ConcurrentLinkedQueue而言,两者兼备,怎么做到的呢?那就是它的线程安全不是通过锁实现的!

1.1 没有锁也能保证线程安全?

  • ConcurrentLinkedQueue是无锁的,使用的是CAS比较交换算法,修改的方法包括三个核心参数(VEN
  • CAS比较交换算法频繁的使用于操作系统之中,对内存进行管理的时候一般不会通过加锁包保证线程安全(效率低),而是使用这种高效的方式保证线程安全。

1.2 CAS算法简介:

比较交换算法:该算法将存储器位置的内容与给定值进行比较,并且只有它们相同时,才将该存储器位置的内容修改为给定的新值。这是作为单个原子操作完成的。原子性保证了新值是根据最新信息计算出来的; 如果在此期间该值已被另一个线程更新,则写入将失败。操作的结果必须表明它是否进行了替换; 这可以通过简单的布尔响应(此变体通常称为比较和设置),或通过返回从内存位置读取的值(而不是写入它的值)来完成。

CAS操作有3个参数:
V:待更新的变量
E:预期值
N:新值
概念实在读不懂…不用着急,有举例
在这里插入图片描述
 看图说话:
 这里海绵宝宝和狗仔为两个线程,美眉是操作系统,两个线程准备往数组下标为4的位置存值;海绵宝宝准备存"D",而狗仔需要存"d",于是两个人需要问一下美眉4号是不是为null(null为两个线程的预期值),如果与线程的预期值一致,两个人分别开始往里面存值;
 存值一定有先后(谁先拿到OS的时间片谁先执行本线程,这里类比到达的先后),若海绵宝宝先到达,发现与预期的一样,里面是null,于是把"D"存进数组的下标4的地方;那么对于狗仔说,当他到达准备放入"d"时,发现预期值变了,不是null,很难过的回家了,记住了里面的值之后,再次询问美眉,4号里面是"D"吗?美眉说“是!”,当狗仔到达后发现4号里的值与预期的一样,因此,就把"D"替换成了"d",如果还不是,继续循环下去;
即:

  • 当只有 V==E时,V=N;否则表示已经被更新过,则取消当前操作;
  • 整个过程没有锁,但是改变值的最终只有一个线程,保证了线程的安全性;

1.3 源码

 对于CAS算法这部分的源码是无法看到的,这部分源码被包含在了一个Java没有公开开源的底层源码,是用其他语言写的,读者可以通过其他渠道获取(openJDK论坛);

1.4 使用

public static void main(String[] args){
    Queue<String> queue = new ConcurrentLinkedQueue<String>();
    queue.offer("A"); //插入"A"
    queue.offer("B");//插入"B"
    queue.poll();//删除"A"
    queue.poll();//获得"B"
}

2. BlockingQueue接口

2.1 方法

基本方法

抛出异常 特殊值 阻塞 超时
添加 add(e) offer(e) put(e) offer(e, time, unit)
删除 remove() poll() take() poll(time, unit)
检查 element() peek() 不可用 不可用

无限期等待方法

方法 描述
void put(E e) 将指定元素插入此队列中,如果没有可用空间,则等待
E take() 获取并移除此队列头部元素,如果没有可用元素,增等待

2.2 生产者消费者模式

 通过常用方法我们可以了解到这是一种生产者和消费者模式,一个存一个取,因此该接口的一大特点是

  • 可以解决生产者、消费者问题

 关于生产者消费者模式类比如下图;详细介绍戳下面的链接:

在这里插入图片描述

2.3 BlockingQueue实现类

 以下实现类的方法与接口用法相似;

ArrayBlockingQueue

  • 数组结构实现,有界队列(用户手动固定上限)
public class TestQueue {
    public static void main(String[] args){
       BlockingQueue<String> objects = new ArrayBlockingQueue<String>(10);//接口引用指向实现类对象
}

LinkedBlockingQueue

  • 链表结构实现,无界队列。(默认上限Integer.MAX_VALUE
public class TestQueue {
    public static void main(String[] args){
        BlockingQueue<String> objects = new ArrayBlockingQueue<String>();//接口引用指向实现类对象
}

三、线程安全的集合总结

在这里插入图片描述

CopyOnWriteArrayList:线程安全的ArrayList

CopyOnWriteArraySet : 线程安全的Set

ConcurrentHashMap: 线程安全的HashMap

ConcurrentLinkedQueue: 线程安全的Queue

发布了89 篇原创文章 · 获赞 465 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/qq_44717317/article/details/105018295