原文地址:http://benjaminwhx.com/2018/05/11/%E3%80%90%E7%BB%86%E8%B0%88Java%E5%B9%B6%E5%8F%91%E3%80%91%E8%B0%88%E8%B0%88LinkedBlockingQueue/

在集合框架里,想必大家都用过ArrayList和LinkedList,也经常在面试中问到他们之间的区别。ArrayList和ArrayBlockingQueue一样,内部基于数组来存放元素,而LinkedBlockingQueue则和LinkedList一样,内部基于链表来存放元素。

LinkedBlockingQueue实现了BlockingQueue接口,这里放一张类的继承关系图(图片来自之前的文章:说说队列Queue

LinkedBlockingQueue不同于ArrayBlockingQueue,它如果不指定容量,默认为Integer.MAX_VALUE,也就是无界队列。所以为了避免队列过大造成机器负载或者内存爆满的情况出现,我们在使用的时候建议手动传一个队列的大小。

2、源码分析

2.1 属性

  1. /**
  2. * 节点类,用于存储数据
  3. */
  4. static class Node<E> {
  5. E item;
  6. Node<E> next;
  7.  
  8. Node(E x) { item = x; }
  9. }
  10.  
  11. /** 阻塞队列的大小,默认为Integer.MAX_VALUE */
  12. private final int capacity;
  13.  
  14. /** 当前阻塞队列中的元素个数 */
  15. private final AtomicInteger count = new AtomicInteger();
  16.  
  17. /**
  18. * 阻塞队列的头结点
  19. */
  20. transient Node<E> head;
  21.  
  22. /**
  23. * 阻塞队列的尾节点
  24. */
  25. private transient Node<E> last;
  26.  
  27. /** 获取并移除元素时使用的锁,如take, poll, etc */
  28. private final ReentrantLock takeLock = new ReentrantLock();
  29.  
  30. /** notEmpty条件对象,当队列没有数据时用于挂起执行删除的线程 */
  31. private final Condition notEmpty = takeLock.newCondition();
  32.  
  33. /** 添加元素时使用的锁如 put, offer, etc */
  34. private final ReentrantLock putLock = new ReentrantLock();
  35.  
  36. /** notFull条件对象,当队列数据已满时用于挂起执行添加的线程 */
  37. private final Condition notFull = putLock.newCondition();

从上面的属性我们知道,每个添加到LinkedBlockingQueue队列中的数据都将被封装成Node节点,添加的链表队列中,其中head和last分别指向队列的头结点和尾结点。与ArrayBlockingQueue不同的是,LinkedBlockingQueue内部分别使用了takeLock 和 putLock 对并发进行控制,也就是说,添加和删除操作并不是互斥操作,可以同时进行,这样也就可以大大提高吞吐量。

这里如果不指定队列的容量大小,也就是使用默认的Integer.MAX_VALUE,如果存在添加速度大于删除速度时候,有可能会内存溢出,这点在使用前希望慎重考虑。

另外,LinkedBlockingQueue对每一个lock锁都提供了一个Condition用来挂起和唤醒其他线程。

构造函数

  1. public LinkedBlockingQueue() {
  2. // 默认大小为Integer.MAX_VALUE
  3. this(Integer.MAX_VALUE);
  4. }
  5.  
  6. public LinkedBlockingQueue(int capacity) {
  7. if (capacity <= 0) throw new IllegalArgumentException();
  8. this.capacity = capacity;
  9. last = head = new Node<E>(null);
  10. }
  11.  
  12. public LinkedBlockingQueue(Collection<? extends E> c) {
  13. this(Integer.MAX_VALUE);
  14. final ReentrantLock putLock = this.putLock;
  15. putLock.lock();
  16. try {
  17. int n = 0;
  18. for (E e : c) {
  19. if (e == null)
  20. throw new NullPointerException();
  21. if (n == capacity)
  22. throw new IllegalStateException("Queue full");
  23. enqueue(new Node<E>(e));
  24. ++n;
  25. }
  26. count.set(n);
  27. } finally {
  28. putLock.unlock();
  29. }
  30. }

默认的构造函数和最后一个构造函数创建的队列大小都为Integer.MAX_VALUE,只有第二个构造函数用户可以指定队列的大小。第二个构造函数最后初始化了last和head节点,让它们都指向了一个元素为null的节点。

方法

同样,LinkedBlockingQueue也有着和ArrayBlockingQueue一样的方法,我们先来看看入队列的方法。

2.3.1、入队方法

LinkedBlockingQueue提供了多种入队操作的实现来满足不同情况下的需求,入队操作有如下几种:

  • void put(E e);
  • boolean offer(E e);
  • boolean offer(E e, long timeout, TimeUnit unit)。

put(E e)

  1. public void put(E e) throws InterruptedException {
  2. if (e == null) throw new NullPointerException();
  3. int c = -1;
  4. Node<E> node = new Node<E>(e);
  5. final ReentrantLock putLock = this.putLock;
  6. final AtomicInteger count = this.count;
  7. // 获取锁中断
  8. putLock.lockInterruptibly();
  9. try {
  10. //判断队列是否已满,如果已满阻塞等待
  11. while (count.get() == capacity) {
  12. notFull.await();
  13. }
  14. // 把node放入队列中
  15. enqueue(node);
  16. c = count.getAndIncrement();
  17. // 再次判断队列是否有可用空间,如果有唤醒下一个线程进行添加操作
  18. if (c + 1 < capacity)
  19. notFull.signal();
  20. } finally {
  21. putLock.unlock();
  22. }
  23. // 如果队列中有一条数据,唤醒消费线程进行消费
  24. if (c == 0)
  25. signalNotEmpty();
  26. }

小结put方法来看,它总共做了以下情况的考虑:

  • 队列已满,阻塞等待。
  • 队列未满,创建一个node节点放入队列中,如果放完以后队列还有剩余空间,继续唤醒下一个添加线程进行添加。如果放之前队列中没有元素,放完以后要唤醒消费线程进行消费。

offer(E e)

  1. public boolean offer(E e) {
  2. if (e == null) throw new NullPointerException();
  3. final AtomicInteger count = this.count;
  4. if (count.get() == capacity)
  5. return false;
  6. int c = -1;
  7. Node<E> node = new Node<E>(e);
  8. final ReentrantLock putLock = this.putLock;
  9. putLock.lock();
  10. try {
  11. // 队列有可用空间,放入node节点,判断放入元素后是否还有可用空间,
  12. // 如果有,唤醒下一个添加线程进行添加操作。
  13. if (count.get() < capacity) {
  14. enqueue(node);
  15. c = count.getAndIncrement();
  16. if (c + 1 < capacity)
  17. notFull.signal();
  18. }
  19. } finally {
  20. putLock.unlock();
  21. }
  22. if (c == 0)
  23. signalNotEmpty();
  24. return c >= 0;
  25. }

可以看到offer仅仅对put方法改动了一点点,当队列没有可用元素的时候,不同于put方法的阻塞等待,offer方法直接方法false。

offer(E e, long timeout, TimeUnit unit)

  1. public boolean offer(E e, long timeout, TimeUnit unit)
  2. throws InterruptedException {
  3.  
  4. if (e == null) throw new NullPointerException();
  5. long nanos = unit.toNanos(timeout);
  6. int c = -1;
  7. final ReentrantLock putLock = this.putLock;
  8. final AtomicInteger count = this.count;
  9. putLock.lockInterruptibly();
  10. try {
  11. // 等待超时时间nanos,超时时间到了返回false
  12. while (count.get() == capacity) {
  13. if (nanos <= 0)
  14. return false;
  15. nanos = notFull.awaitNanos(nanos);
  16. }
  17. enqueue(new Node<E>(e));
  18. c = count.getAndIncrement();
  19. if (c + 1 < capacity)
  20. notFull.signal();
  21. } finally {
  22. putLock.unlock();
  23. }
  24. if (c == 0)
  25. signalNotEmpty();
  26. return true;
  27. }

该方法只是对offer方法进行了阻塞超时处理,使用了Condition的awaitNanos来进行超时等待,这里为什么要用while循环?因为awaitNanos方法是可中断的,为了防止在等待过程中线程被中断,这里使用while循环进行等待过程中中断的处理,继续等待剩下需等待的时间。

Java中的阻塞队列-LinkedBlockingQueue(二)的更多相关文章

  1. 聊聊并发(七)——Java中的阻塞队列

    3. 阻塞队列的实现原理 聊聊并发(七)--Java中的阻塞队列 作者 方腾飞 发布于 2013年12月18日 | ArchSummit全球架构师峰会(北京站)2016年12月02-03日举办,了解更 ...

  2. Java中的阻塞队列(BlockingQueue)

    1. 什么是阻塞队列 阻塞队列(BlockingQueue)是 Java 5 并发新特性中的内容,阻塞队列的接口是 java.util.concurrent.BlockingQueue,它提供了两个附 ...

  3. Java中的阻塞队列-ArrayBlockingQueue(一)

    最近在看一些java基础的东西,看到了队列这章,打算对复习的一些知识点做一个笔记,也算是对自己思路的一个整理,本章先聊聊java中的阻塞队列 参考文章: http://ifeve.com/java-b ...

  4. 多线程编程学习六(Java 中的阻塞队列).

    介绍 阻塞队列(BlockingQueue)是指当队列满时,队列会阻塞插入元素的线程,直到队列不满:当队列空时,队列会阻塞获得元素的线程,直到队列变非空.阻塞队列就是生产者用来存放元素.消费者用来获取 ...

  5. 阻塞队列一——java中的阻塞队列

    目录 阻塞队列简介:介绍阻塞队列的特性与应用场景 java中的阻塞队列:介绍java中实现的供开发者使用的阻塞队列 BlockQueue中方法:介绍阻塞队列的API接口 阻塞队列的实现原理:具体的例子 ...

  6. JUC之Java中的阻塞队列及其实现原理

    在文章线程池实现原理 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中介绍了线程池的组成部分,其中一个组成部分就是阻塞队列.那么JAVA中的阻塞队列如何实现的呢? 阻塞队列,关键字是阻塞 ...

  7. java并发之阻塞队列LinkedBlockingQueue与ArrayBlockingQueue

    Java中阻塞队列接口BlockingQueue继承自Queue接口,并提供put.take阻塞方法.两个主要的阻塞类实现是ArrayBlockingQueue和LinkedBlockingQueue ...

  8. Java中的阻塞队列

    1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用 ...

  9. java 中的阻塞队列

    1.什么是阻塞队列: 支持阻塞的插入方法,意思是当队列满时,队列会阻塞插入元素的线程,知道队列不满. 支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空. 插入和移除操作的4种处 ...

随机推荐

  1. 大佬写的js生成玫瑰(来源网络)

    <!DOCTYPE html> <html> <head> <title>js html5渲染的3D玫瑰花(程序员的情人节礼物)</title&g ...

  2. 51nod1478(yy)

    题目链接: http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1478&judgeId=365133 题意: 中文题诶 ...

  3. 前端性能优化-gzip

    为什么要开启GZIP 我们需要下载一个100KB的Javascript文件,正常的下载量就是100KB,如果我们把文件在服务端压缩一下,压缩成30kb,下载到客户端再进行解压,这样就减少了大量的HTT ...

  4. linux文件系统相关资料

             linux下文件系统通常是通过虚拟文件系统(VFS)蔽下层具体文件系统操作的差异,为上层的操作提供一个统一的接口.文件系统底层都是用系统IO缓存层提供的块读写接口,实现逻辑块到物理块 ...

  5. P4013 数字梯形问题

    \(\color{#0066ff}{题目描述}\) 给定一个由 \(n\) 行数字组成的数字梯形如下图所示. 梯形的第一行有 \(m\) 个数字.从梯形的顶部的 \(m\) 个数字开始,在每个数字处可 ...

  6. IP 地址分类

    1.1 网络IP地址分类 网络通讯过程中数据封装与解封过程(网际互联通讯过程) TCP/IP模型 1)应用层 总结记录一些常见网络协议以及对应的端口号(FTP HTTP telnet) 2)主机到主机 ...

  7. sklearn——回归评估指标

    sklearn中文文档:http://sklearn.apachecn.org/#/ https://www.cnblogs.com/nolonely/p/7009001.html https://w ...

  8. ubuntu replace system openjdk

    一些ubuntu自带jdk的.但是有时候会确实我们所要的文件.下面介绍如何replace jdk 1. 卸载现有jdk sudo apt-get purge openjdk-\* 2. 下载jdk. ...

  9. PIXI 写一个字及图片保存(2)

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  10. Win7 IIS 局域网中无法访问网页

    安装好iis后,在局域网中无法浏览网页一,关闭防火墙即可 或者建立入站规则 打开控制面板——window防火墙——高级设置 在入站规则上右键新建入站规则,选择端口然后下一步 选择tcp和特定端口在端口 ...