Java 中的阻塞队列详细介绍
这里有新鲜出炉的 Java 并发编程示例, 程序狗速度看过来!
Java 程序设计语言
java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言, 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台 (即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se)) 的总称
这篇文章主要介绍了 Java 中的阻塞队列详细介绍的相关资料, 需要的朋友可以参考下
Java 中的阻塞队列
1. 什么是阻塞队列?
阻塞队列 (BlockingQueue) 是一个支持两个附加操作的队列这两个附加的操作是:
在队列为空时, 获取元素的线程会等待队列变为非空
当队列满时, 存储元素的线程会等待队列可用
阻塞队列常用于生产者和消费者的场景, 生产者是往队列里添加元素的线程, 消费者是从队列里拿元素的线程阻塞队列就是生产者存放元素的容器, 而消费者也只从容器里拿元素
2.Java 里的阻塞队列
JDK 中提供了七个阻塞队列:
ArrayBlockingQueue : 一个由数组结构组成的有界阻塞队列
LinkedBlockingQueue : 一个由链表结构组成的有界阻塞队列
PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列
DelayQueue: 一个使用优先级队列实现的无界阻塞队列
SynchronousQueue: 一个不存储元素的阻塞队列
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列
ArrayBlockingQueue
ArrayBlockingQueue 是一个用数组实现的有界阻塞队列此队列按照先进先出 (FIFO) 的原则对元素进行排序默认情况下不保证访问者公平的访问队列, 所谓公平访问队列是指阻塞的所有生产者线程或消费者线程, 当队列可用时, 可以按照阻塞的先后顺序访问队列, 即先阻塞的生产者线程, 可以先往队列里插入元素, 先阻塞的消费者线程, 可以先从队列里获取元素通常情况下为了保证公平性会降低吞吐量我们可以使用以下代码创建一个公平的阻塞队列:
ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
而对于其访问的公平性, 是通过 ReentrantLock 锁来实现的
LinkedBlockingQueue
LinkedBlockingQueue 是一个用链表实现的有界阻塞队列此队列的默认和最大长度为 Integer.MAX_VALUE 此队列按照先进先出的原则对元素进行排序
PriorityBlockingQueue
PriorityBlockingQueue 是一个支持优先级的无界队列默认情况下元素采取自然顺序排列, 也可以通过比较器 comparator 来指定元素的排序规则元素按照升序排列
DelayQueue
DelayQueue 是一个支持延时获取元素的无界阻塞队列队列使用 PriorityQueue 来实现队列中的元素必须实现 Delayed 接口, 在创建元素时可以指定多久才能从队列中获取当前元素只有在延迟期满时才能从队列中提取元素我们可以将 DelayQueue 运用在以下应用场景:
缓存系统的设计: 可以用 DelayQueue 保存缓存元素的有效期, 使用一个线程循环查询 DelayQueue, 一旦能从 DelayQueue 中获取元素时, 表示缓存有效期到了
定时任务调度使用 DelayQueue 保存当天将会执行的任务和执行时间, 一旦从 DelayQueue 中获取到任务就开始执行, 从比如 TimerQueue 就是使用 DelayQueue 实现的
如何实现 Delayed 接口
我们可以参考 ScheduledThreadPoolExecutor 里 ScheduledFutureTask 类这个类实现了 Delayed 接口首先: 在对象创建的时候, 使用 time 记录前对象什么时候可以使用, 代码如下:
- ScheduledFutureTask(Runnable r, V result, long ns, long period) {
- super(r, result);
- this.time = ns;
- this.period = period;
- this.sequenceNumber = sequencer.getAndIncrement();
- }
然后使用 getDelay 可以查询当前元素还需要延时多久, 代码如下:
- public long getDelay(TimeUnit unit) {
- return unit.convert(time - now(), TimeUnit.NANOSECONDS);
- }
通过构造函数可以看出延迟时间参数 ns 的单位是纳秒, 自己设计的时候最好使用纳秒, 因为 getDelay 时可以指定任意单位, 一旦以纳秒作为单位, 而延时的时间又精确不到纳秒就麻烦了使用时请注意当 time 小于当前时间时, getDelay 会返回负数
最后我们可以使用 time 的来指定其在队列中的顺序, 例如: 让延时时间最长的放在队列的末尾
- public int compareTo(Delayed other) {
- if (other == this)
- return 0;
- if (other instanceof ScheduledFutureTask) {
- ScheduledFutureTask x = (ScheduledFutureTask)other;
- long diff = time - x.time;
- if (diff < 0)
- return -1;
- else if (diff > 0)
- return 1;
- else if (sequenceNumber < x.sequenceNumber)
- return -1;
- else
- return 1;
- }
- long d = (getDelay(TimeUnit.NANOSECONDS)-other.getDelay(TimeUnit.NANOSECONDS));
- return (d == 0) ? 0 : ((d < 0) ? -1 : 1);
- }
如何实现延时阻塞队列
延时阻塞队列的实现很简单, 当消费者从队列里获取元素时, 如果元素没有达到延时时间, 就阻塞当前线程
- long delay = first.getDelay(TimeUtil.NANOSECONDS);
- if (delay <= 0) {
- return q.poll; // 阻塞队列
- } else if (leader != null) {
- //leader 表示一个等待从阻塞队列中取消息的线程
- available.await(); // 让线程进入等待信号
- } else {
- // 当 leader 为 null, 则将当前线程设置为 leader
- Thread thisThread = Thread.currentThread();
- try {
- leader = thisThread;
- // 使用 awaitNanos()方法让当前线程等待接收信号或等待 delay 时间
- available.awaitNanos(delay);
- } finally {
- if (leader == thisThread) {
- leader = null;
- }
- }
- }
- SynchronousQueue
SynchronousQueue 是一个不存储元素的阻塞队列每一个 put 操作必须等待一个 take 操作, 否则不能继续添加元素 SynchronousQueue 可以看成是一个传球手, 负责把生产者线程处理的数据直接传递给消费者线程队列本身并不存储任何元素, 非常适合于传递性场景, 比如在一个线程中使用的数据, 传递给另外一个线程使用, SynchronousQueue 的吞吐量高于
LinkedBlockingQueue 和 ArrayBlockingQueue
它支持公平访问队列默认情况下依然是非公平性的策略机制
LinkedTransferQueue
LinkedTransferQueue 是一个由链表结构组成的无界阻塞 TransferQueue 队列相对于其他阻塞队列 LinkedTransferQueue 多了 tryTransfer 和 transfer 方法
transfer 方法
如果当前有消费者正在等待接收元素 (消费者使用 take() 方法或带时间限制的 poll()方法时),transfer 方法可以把生产者传入的元素立刻 transfer(传输)给消费者如果没有消费者在等待接收元素, transfer 方法会将元素存放在队列的 tail 节点, 并等到该元素被消费者消费了才返回
tryTransfer 方法
是用来试探下生产者传入的元素是否能直接传给消费者如果没有消费者等待接收元素, 则返回 false 和 transfer 方法的区别是 tryTransfer 方法无论消费者是否接收, 方法立即返回而 transfer 方法是必须等到消费者消费了才返回
对于带有时间限制的 tryTransfer(E e, long timeout, TimeUnit unit)方法, 则是试图把生产者传入的元素直接传给消费者, 但是如果没有消费者消费该元素则等待指定的时间再返回, 如果超时还没消费元素, 则返回 false, 如果在超时时间内消费了元素, 则返回 true
LinkedBlockingDeque
LinkedBlockingDeque 是一个由链表结构组成的双向阻塞队列所谓双向队列指的你可以从队列的两端插入和移出元素双端队列因为多了一个操作队列的入口, 在多线程同时入队时, 也就减少了一半的竞争相比其他的阻塞队列, LinkedBlockingDeque 多了 addFirst,addLast,offerFirst,offerLast,peekFirst,peekLast 等方法, 以 First 单词结尾的方法, 表示插入, 获取 (peek) 或移除双端队列的第一个元素以 Last 单词结尾的方法, 表示插入, 获取或移除双端队列的最后一个元素另外插入方法 add 等同于 addLast, 移除方法 remove 等效于 removeFirst 但是 take 方法却等同于 takeFirst, 不知道是不是 Jdk 的 bug, 使用时还是用带有 First 和 Last 后缀的方法更清楚在初始化 LinkedBlockingDeque 时可以初始化队列的容量, 用来防止其再扩容时过渡膨胀另外双向阻塞队列可以运用在工作窃取模式中
来源: http://www.phperz.com/article/18/0205/359547.html