type
status
date
slug
summary
tags
category
icon
password
notion image
先来看一下Queue的定义:
这些方法的区别:
操作
抛出异常
返回具体值
插入
add(e)
offer(e)
移除
remove()
poll()
检查(只查看)
element()
peek()

BlockingQueue

BlockingQueue也是一个接口,继承自Queue。

如何理解这个“blocking”?

  • *对于插入(入队)操作:**如果是无边界的队列,则直接插入;如果是有边界的队列,则需要等待队列中又空余容量的时候才能插入。
  • *对于出队操作:**当队列不为空时,才会出队。
BlockingQueue还有如下特点:
  • 线程安全
  • 不允许null值

LinkedBlockingQueue

这是基于链表的BlockingQueue实现。
这里还有一个吞吐量的概念:队列每秒可以处理的消息数量,包括两个方面,一方面是发送的数量,一方面是接收的数量。
可以在构造方法传入一个容量,用来避免队列过度扩大,如果不指定容量,默认容量就位Integer.MAX_VALUE
通过ReentranceLockCondition来保证线程安全。
先来看一下链表节点:
还有一些主要的fields:
由此知道LinkedBlockingQueue中包含了元素个数,容量边界,以及头尾节点等filed,还包含了用来保证线程安全性的takeLock,putLock,以及条件队列notEmpty和notFull。

插入

可以看到,队列不允许null值,并且通过ReentrantLock和Condition保证线程安全。

移除

操作和插入操作差不多。

ArrayBlockingQueue

基于数组实现的BlockingQueue。
属于典型的有界缓冲区,有固定的容量,一旦创建,容量无法修改。
在构造方法中必须传入一个容量参数,还可以传入一个参数用来指示队列的公平性访问问题;
可以看到,传入的fair参数实际上是用在ReentrantLock上的,用来表示用的是公平锁,还是非公平锁。
如果一个线程申请一把公平锁,那么当锁释放的时候,先申请的线程先得到锁,非常公平;
如果一个线程申请的是一把非公平锁,那么当锁释放的时候,后申请的线程可能会先得到锁,有随机性;
使用非公平锁的队列的吞吐量比使用公平锁的对垒的吞吐量要大,通常情况下都是优先使用非公平锁。
对于synchronized关键字来说,它也是一种非公平锁,而且没有任何方式可以将它变成公平锁。

插入

移除

SynchronousQueue

SynchronousQueue也是一种BlockingQueue,但是它比较特殊,它有如下特点:
  • 并不存储任何元素,即容量为0,数据直接在配对的生产者/消费者之间传递,不会输入到缓冲队列中;
  • 入队和出队线程必须一一匹配,要不然先到的线程会阻塞;
  • 支持公平,非公平策略,默认为非公平策略。公平策略,基于内部的TransferStack结构实现,非公平策略基于内部的TransferQueue结构实现;
  • 基于一种无锁算法实现
从构造方法中可以看出其公平策略不同导致实现的不同:
这两个数据结构均是内部抽象类Transfer的实现:
再来看一下它的size方法:
不会存储任何元素,容量为0.

插入

移除

PriorityQueue

基于数组(平衡二叉堆,优先堆)实现,元素是有序的,你需要实现Comparable,以方便元素进行比较,如果不实现,则按照自然顺序,如果队列不为空,则第一个元素为最小的元素。
这是一个无界的队列,默认容量大小为11,添加元素的时候会进行扩容。
总结一下特点吧:
  • 基于数组实现,因而可以自由扩容,属于无界队列
  • 有序(根据优先级,而优先级是通过Comparable实现进行比较),小的元素再前面
  • 线程不安全
  • 不支持null值

插入

可以看到其中有扩容操作,还有对堆的操作:

扩容方式

移除

依然是对数组的处理,关键点在处理堆的过程上。

PriorityBlockingQueue

PriorityBlockingQueue是一个阻塞队列,既然是阻塞队列,那么肯定有阻塞操作put和take。同时也是线程安全的。
它也是基于数组实现的,和PriorityQueue的存储结构无异,区别有以下几点:
  • PriorityBlockingQueue是线程安全的(ReentrantLock保证);
  • 支持阻塞操作put和take

DelayQueue

DelayQueue也是一个BlockingQueue,内部存储基于PriorityQueue,并通过ReentrantLock保证线程安全操作。
它有如下特点:
  • 支持延迟获取元素(在创建的时候有一个过期时间,过期之后才可以获取元素)
  • PriorityQueue是无界的,因此DelayQueue也是无界的
  • 线程安全的
插入的元素类型必须实现Delay接口:
关键在于出队操作:
使用DelayQueue最常见的的场景有:
  • 缓存系统的设计
    • 使用DelayQueue保存带有效期的缓存元素,使用一个线程轮询DelayQueue,一旦获取到元素则表示缓存到期了;
  • 定时任务调度
    • 使用DelayQueue保存当天要执行的任务和时间,一旦获取到任务就开始执行,Timer中的TimingQueue就是基于DelayQueue实现的。

使用场景总结

队列一般用作基于生产-消费者模型的系统中,下面举一些比较常见的例子:
  • 线程池的设计
    • JDK中的线程池设计使用到了各种BlockingQueue的实现,用来作为线程的容器。
      比如固定线程数量和单个线程的ExecutorService,使用的是LinkedBlockingQueue;
      缓存线程个数的ExecutorService,使用的是SynchronousQueue;
  • 缓存系统的设计
    • 比如前面提到的DelayQueue,元素可以带缓存时间。
  • 定时调度的设计(如Timer)
    • 比如前面提到的DelayQueue
  • Android Framework中队列的使用
    • Android 的AsyncLayoutInflater中使用的ArrayBlockingQueue;
    • AnimationThread中使用了PriorityQueue;
Java反射Android Hook之拦截Activity的启动
Loading...