LinkedBlockingQueue介绍

  【1】LinkedBlockingQueue是一个基于链表实现的阻塞队列,默认情况下,该阻塞队列的大小为Integer.MAX_VALUE,由于这个数值特别大,所以 LinkedBlockingQueue 也被称作无界队列,代表它几乎没有界限,队列可以随着元素的添加而动态增长,但是如果没有剩余内存,则队列将抛出OOM错误。所以为了避免队列过大造成机器负载或者内存爆满的情况出现,我们在使用的时候建议手动传一个队列的大小。

  【2】LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素。LinkedBlockingQueue采用两把锁的锁分离技术实现入队出队互不阻塞,添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。

LinkedBlockingQueue使用

  1. //指定队列的大小创建有界队列
  2. BlockingQueue<Integer> boundedQueue = new LinkedBlockingQueue<>(100);
  3. //无界队列
  4. BlockingQueue<Integer> unboundedQueue = new LinkedBlockingQueue<>();

LinkedBlockingQueue的源码分析

  【1】属性值

  1. // 容量,指定容量就是有界队列
  2. private final int capacity;
  3. // 元素数量,用原子操作类的原因在于有两个线程都会操作需要保证可见性
  4. private final AtomicInteger count = new AtomicInteger();
  5. // 链表头 本身是不存储任何元素的,初始化时item指向null
  6. transient Node<E> head;
  7. // 链表尾
  8. private transient Node<E> last;
  9. // take锁 锁分离,提高效率
  10. private final ReentrantLock takeLock = new ReentrantLock();
  11. // notEmpty条件
  12. // 当队列无元素时,take锁会阻塞在notEmpty条件上,等待其它线程唤醒
  13. private final Condition notEmpty = takeLock.newCondition();
  14. // put锁
  15. private final ReentrantLock putLock = new ReentrantLock();
  16. // notFull条件
  17. // 当队列满了时,put锁会会阻塞在notFull上,等待其它线程唤醒
  18. private final Condition notFull = putLock.newCondition();
  19.  
  20. //典型的单链表结构
  21. static class Node<E> {
  22. E item; //存储元素
  23. Node<E> next; //后继节点 单链表结构
  24. Node(E x) { item = x; }
  25. }

  【2】构造函数

  1. public LinkedBlockingQueue() {
  2. // 如果没传容量,就使用最大int值初始化其容量
  3. this(Integer.MAX_VALUE);
  4. }
  5.  
  6. public LinkedBlockingQueue(int capacity) {
  7. if (capacity <= 0) throw new IllegalArgumentException();
  8. this.capacity = capacity;
  9. // 初始化head和last指针为空值节点
  10. last = head = new Node<E>(null);
  11. }
  12.  
  13. public LinkedBlockingQueue(Collection<? extends E> c) {
  14. this(Integer.MAX_VALUE);
  15. final ReentrantLock putLock = this.putLock;
  16. putLock.lock(); // 为保证可见性而加的锁
  17. try {
  18. int n = 0;
  19. for (E e : c) {
  20. if (e == null)
  21. throw new NullPointerException();
  22. if (n == capacity)
  23. throw new IllegalStateException("Queue full");
  24. enqueue(new Node<E>(e));
  25. ++n;
  26. }
  27. count.set(n);
  28. } finally {
  29. putLock.unlock();
  30. }
  31. }

  【3】核心方法分析

    1)入队put方法

  1. public void put(E e) throws InterruptedException {
  2. // 不允许null元素
  3. if (e == null) throw new NullPointerException();
  4. int c = -1;
  5. // 新建一个节点
  6. Node<E> node = new Node<E>(e);
  7. final ReentrantLock putLock = this.putLock;
  8. final AtomicInteger count = this.count;
  9. // 使用put锁加锁
  10. putLock.lockInterruptibly();
  11. try {
  12. // 如果队列满了,就阻塞在notFull上等待被其它线程唤醒(阻塞生产者线程)
  13. while (count.get() == capacity) {
  14. notFull.await();
  15. }
  16. // 队列不满,就入队
  17. enqueue(node);
  18. c = count.getAndIncrement();// 队列长度加1,返回原值
  19. // 如果现队列长度小于容量,notFull条件队列转同步队列,准备唤醒一个阻塞在notFull条件上的线程(可以继续入队)
  20. // 这里为啥要唤醒一下呢?
  21. // 因为可能有很多线程阻塞在notFull这个条件上,而取元素时只有取之前队列是满的才会唤醒notFull,此处不用等到取元素时才唤醒
  22. if (c + 1 < capacity)
  23. notFull.signal();
  24. } finally {
  25. putLock.unlock(); // 真正唤醒生产者线程
  26. }
  27. // 如果原队列长度为0,现在加了一个元素后立即唤醒阻塞在notEmpty上的线程
  28. if (c == 0)
  29. signalNotEmpty();
  30. }
  31. private void enqueue(Node<E> node) {
  32. // 直接加到last后面,last指向入队元素
  33. last = last.next = node;
  34. }
  35. private void signalNotEmpty() {
  36. final ReentrantLock takeLock = this.takeLock;
  37. takeLock.lock();// 加take锁
  38. try {
  39. notEmpty.signal();// notEmpty条件队列转同步队列,准备唤醒阻塞在notEmpty上的线程
  40. } finally {
  41. takeLock.unlock(); // 真正唤醒消费者线程
  42. }
  43. }

    2)出队take方法

  1. public E take() throws InterruptedException {
  2. E x;
  3. int c = -1;
  4. final AtomicInteger count = this.count;
  5. final ReentrantLock takeLock = this.takeLock;
  6. // 使用takeLock加锁
  7. takeLock.lockInterruptibly();
  8. try {
  9. // 如果队列无元素,则阻塞在notEmpty条件上(消费者线程阻塞)
  10. while (count.get() == 0) {
  11. notEmpty.await();
  12. }
  13. // 否则,出队
  14. x = dequeue();
  15. c = count.getAndDecrement();//长度-1,返回原值
  16. if (c > 1)// 如果取之前队列长度大于1,notEmpty条件队列转同步队列,准备唤醒阻塞在notEmpty上的线程,原因与入队同理
  17. notEmpty.signal();
  18. } finally {
  19. takeLock.unlock(); // 真正唤醒消费者线程
  20. }
  21. // 为什么队列是满的才唤醒阻塞在notFull上的线程呢?
  22. // 因为唤醒是需要加putLock的,这是为了减少锁的次数,所以,这里索性在放完元素就检测一下,未满就唤醒其它notFull上的线程,
  23. // 这也是锁分离带来的代价
  24. // 如果取之前队列长度等于容量(已满),则唤醒阻塞在notFull的线程
  25. if (c == capacity)
  26. signalNotFull();
  27. return x;
  28. }
  29. private E dequeue() {
  30. // head节点本身是不存储任何元素的
  31. // 这里把head删除,并把head下一个节点作为新的值
  32. // 并把其值置空,返回原来的值
  33. Node<E> h = head;
  34. Node<E> first = h.next;
  35. h.next = h; // 方便GC
  36. head = first;
  37. E x = first.item;
  38. first.item = null;
  39. return x;
  40. }
  41. private void signalNotFull() {
  42. final ReentrantLock putLock = this.putLock;
  43. putLock.lock();
  44. try {
  45. notFull.signal();// notFull条件队列转同步队列,准备唤醒阻塞在notFull上的线程
  46. } finally {
  47. putLock.unlock(); // 解锁,这才会真正的唤醒生产者线程
  48. }
  49. }

LinkedBlockingQueue总结

  【1】无界阻塞队列,可以指定容量,默认为 Integer.MAX_VALUE,先进先出,存取互不干扰

  【2】数据结构:链表(可以指定容量,默认为 Integer.MAX_VALUE,内部类Node存储元素)

  【3】锁分离:存取互不干扰,存取操作的是不同的Node对象(takeLock【取Node节点保证前驱后继不乱】,putLock【存Node节点保证前驱后继不乱】,删除时则两个锁一起加)【这是最大的亮点】

  【4】阻塞对象(notEmpty【出队:队列count=0,无元素可取时,阻塞在该对象上】,notFull【入队:队列count=capacity,放不进元素时,阻塞在该对象上】)

  【5】入队,从队尾入队,由last指针记录。

  【6】出队,从队首出队,由head指针记录。

  【7】线程池中采用LinkedBlockingQueue而不采用ArrayBlockingQueue的原因便是因为锁分离带来了性能的提升,大大提高队列的吞吐量。

LinkedBlockingQueue详解的更多相关文章

  1. 20.并发容器之ArrayBlockingQueue和LinkedBlockingQueue实现原理详解

    1. ArrayBlockingQueue简介 在多线程编程过程中,为了业务解耦和架构设计,经常会使用并发容器用于存储多线程间的共享数据,这样不仅可以保证线程安全,还可以简化各个线程操作.例如在“生产 ...

  2. JAVA线程池原理详解二

    Executor框架的两级调度模型 在HotSpot VM的模型中,JAVA线程被一对一映射为本地操作系统线程.JAVA线程启动时会创建一个本地操作系统线程,当JAVA线程终止时,对应的操作系统线程也 ...

  3. ThreadPoolExecutor运转机制详解

    ThreadPoolExecutor运转机制详解 - 走向架构师之路 - 博客频道 - CSDN.NET 最近发现几起对ThreadPoolExecutor的误用,其中包括自己,发现都是因为没有仔细看 ...

  4. java线程池的使用与详解

    java线程池的使用与详解 [转载]本文转载自两篇博文:  1.Java并发编程:线程池的使用:http://www.cnblogs.com/dolphin0520/p/3932921.html   ...

  5. 详解Executor框架

    在Java中,使用线程来异步执行任务.Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建与销毁将消耗大量的计算资源.同时,为每一个任务创建一个新线程来执行 ...

  6. Java多线程学习之线程池源码详解

    0.使用线程池的必要性 在生产环境中,如果为每个任务分配一个线程,会造成许多问题: 线程生命周期的开销非常高.线程的创建和销毁都要付出代价.比如,线程的创建需要时间,延迟处理请求.如果请求的到达率非常 ...

  7. 【Java多线程】Executor框架的详解

    在Java中,使用线程来异步执行任务.Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建与销毁将消耗大量的计算资源.同时,为每一个任务创建一个新线程来执行 ...

  8. Java:多线程,线程池,ThreadPoolExecutor详解

    1. ThreadPoolExecutor的一个常用的构造方法 ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepA ...

  9. Spring cloud Hystrix的配置属性优先级和详解

    Hystrix配置属性详解 Hystrix可以配置属性的有以下类型: Execution:控制HystrixCommand.run() 的如何执行 Fallback: 控制HystrixCommand ...

随机推荐

  1. 使用gulp助力前端自动化

    前言 随着前端诸如webpack,rollup,vite的发展,gulp感觉似乎好像被取代了.其实并没有,只不过它从台前退居到了幕后.我们仍然可以在很多项目中看到它的身影,比如elementplus. ...

  2. 青峰Flutter视频播放软件

    下载地址: https://github.com/patton88/peak_flutter_player/raw/master/peak_flutter_player_v1.1.5_release0 ...

  3. 日均 6000+ 实例,TB 级数据流量,Apache DolphinScheduler 如何做联通医疗大数据平台的“顶梁柱”?

    作者 | 胡泽康 鄞乐炜 作者简介 胡泽康 联通(广东)产业互联网公司  大数据工程师,专注于开源大数据领域,从事大数据平台研发工作 鄞乐炜 联通(广东)产业互联网公司 大数据工程师,主要从事大数据平 ...

  4. docker compose搭建redis7.0.4高可用一主二从三哨兵集群并整合SpringBoot【图文完整版】

    一.前言 redis在我们企业级开发中是很常见的,但是单个redis不能保证我们的稳定使用,所以我们要建立一个集群. redis有两种高可用的方案: High availability with Re ...

  5. 基于Anacoda搭建虚拟环境cudnn6.0+cuda8.0+python3.6+tensorflow-gpu1.4.0

    !一定要查准cudnn,cuda,tensorflow-gpu对应的版本号再进行安装,且本文一切安装均在虚拟环境中完成. 下文以笔者自己电脑为例,展开安装教程阐述(省略anaconda安装教程): 1 ...

  6. 【NOI P模拟赛】仙人掌(圆方树,树形DP)

    题面 n n n 个点, m m m 条边. 1 ≤ n ≤ 1 0 5 , n − 1 ≤ m ≤ 2 × 1 0 5 1\leq n\leq 10^5,n-1\leq m\leq 2\times1 ...

  7. 《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(15)-Fiddler弱网测试,知否知否,应是必知必会

    1.简介 现在这个时代已经属于流量时代,用户对于App或者小程序之类的操作界面的数据和交互的要求也越来越高.对于测试人员弱网测试也是需要考验自己专业技术能力的一种技能.一个合格的测试人员,需要额外关注 ...

  8. 巧用 transition 实现短视频 APP 点赞动画

    在各种短视频界面上,我们经常会看到类似这样的点赞动画: 非常的有意思,有意思的交互会让用户更愿意进行互动. 那么,这么有趣的点赞动画,有没有可能使用纯 CSS 实现呢?那当然是必须的,本文,就将巧妙的 ...

  9. 关于 Math.random()生成指定范围内的随机数的公式推导

    关于 Math.random()生成指定范围内的随机数的公式推导 在 java 中,用于生成随机数的 Math 方法 random()只能生成 0-1 之间的随机数,而对于生成指定区间,例如 a-b ...

  10. 第六十一篇:Vue的绑定事件和修饰符

    好家伙,补基础加实践 1.绑定事件 我们使用v-on(简写为@)来绑定事件 写个例子, 按钮绑定数字加一(太tm经典了) 在<button>元素中使用@点击事件绑定方法"的&qu ...