zoukankan      html  css  js  c++  java
  • LinkedBlockingQueue 和 ConcurrentLinkedQueue的区别

    1. 简单的开篇

    LinkedBlockingQueueConcurrentLinkedQueue 是 Java 高并发场景中最常使用的队列。尽管这两个队列经常被用作并发场景的数据结构,但它们之间仍有细微的特征和行为差异。
    在这篇文章中,我将和大家一起探讨这两者之间的异同点。欢迎大家在留言讨论~

    2. LinkedBlockingQueue

    首先 LinkedBlockingQueue 是一个 “可选且有界” 的阻塞队列实现,你可以根据需要指定队列的大小。
    接下来,我将创建一个LinkedBlockingQueue,它最多可以包含100个元素:

    BlockingQueue<Integer> boundedQueue = new LinkedBlockingQueue<>(100);
    

    当然,我们也可以通过不指定大小,来创建一个无界的 LinkedBlockingQueue

    BlockingQueue<Integer> unboundedQueue = new LinkedBlockingQueue<>();
    

    无界队列表示在创建时未指定队列的大小。因此,队列可以随着元素的添加而动态增长。但是,如果没有剩余内存,则队列将抛出 java.lang.OutOfMemory 错误。

    这里留下一个问题给大家思考: 创建无界队列是好还是坏呢?

    我们还可以从现有的集合来创建 LinkedBlockingQueue

    Collection<Integer> listOfNumbers = Arrays.asList(1,2,3,4,5);
    BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(listOfNumbers);
    

    LinkedBlockingQueue 实现了BlockingQueue接口,该接口为它提供了阻塞性质

    阻塞队列表示如果访问线程已满(当队列有界时)或变为空,则队列将阻塞该线程。如果队列已满,则添加新元素将阻塞访问线程,除非新元素有可用空间。类似地,如果队列为空,则访问元素会阻塞调用线程:

    ExecutorService executorService = Executors.newFixedThreadPool(1);
    LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
    executorService.submit(() -> {
      try {
        queue.take();
      } 
      catch (InterruptedException e) {
        // exception handling
      }
    });
    

    在上面的代码片段中,我们正在访问一个空队列。因此,take() 方法阻塞调用线程。

    LinkedBlockingQueue 的阻塞特性与一些开销相关。这个代价是因为每个puttake操作在生产者线程或使用者线程之间都是锁争用的。因此,在许多生产者和消费者的情况下,put和take 动作可能会慢一些。

    3. ConcurrentLinkedQueue

    首先声明,ConcurrentLinkedQueue 是一个无边界、线程安全且无阻塞的队列

    创建一个空的 ConcurrentLinkedQueue:

    ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
    

    同上面一样,我们也可以从现有集合创建 ConcurrentLinkedQueue

    Collection<Integer> listOfNumbers = Arrays.asList(1,2,3,4,5);
    ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>(listOfNumbers);
    

    不同于 LinkedBlockingQueueConcurrentLinkedQueue是非阻塞的队列。因此,即使队列为空(empty),它也不会阻塞线程。相反,它会返回 (null) 。虽然它是无界的,但如果没有额外的内存来添加新元素,它依旧会抛出 java.lang.OutOfMemory 错误。
    除了非阻塞之外,ConcurrentLinkedQueue还有其他特性。
    在任何生产者-消费者场景中,消费者都不会满足于生产者;但是,多个生产者将相互竞争:

    int element = 1;
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
     
    Runnable offerTask = () -> queue.offer(element);
     
    Callable<Integer> pollTask = () -> {
      while (queue.peek() != null) {
        return queue.poll().intValue();
      }
      return null;
    };
     
    executorService.submit(offerTask);
    Future<Integer> returnedElement = executorService.submit(pollTask);
    assertThat(returnedElement.get().intValue(), is(equalTo(element)));
    

    第一个任务 offerTask 向队列中添加元素,第二个任务 pollTask 从队列中检索元素。pollTask 首先检查队列中的元素,因为ConcurrentLinkedQueue是非阻塞的,并且可以返回null

    4. 求同

    LinkedBlockingQueueConcurrentLinkedQueue 都是队列实现,并具有一些共同特征。让我们讨论一下这两个队列的相似之处:

    1. 实现 Queue 接口
    2. 它们都使用 linked nodes 存储节点
    3. 都适用于并发访问场景

    5. 存异

    尽管这两种队列都有某些相似之处,但也有一些实质性的特征差异:

    特性 LinkedBlockingQueue ConcurrentLinkedQueue
    阻塞性 阻塞队列,并实现blocking queue接口 非阻塞队列,不实现blocking queue接口
    队列大小 可选的有界队列,这意味着可以在创建期间定义队列大小 无边界队列,并且没有在创建期间指定队列大小的规定
    锁特性 基于锁的队列 无锁队列
    算法 锁的实现基于 “双锁队列(two lock queue)” 算法 依赖于Michael&Scott算法来实现无阻塞、无锁队列
    实现 双锁队列 算法机制中,LinkedBlockingQueue使用两种不同的锁,putLocktakeLockput/take操作使用第一个锁类型,take/poll操作使用另一个锁类型 使用CAS(Compare And Swap)进行操作
    阻塞行为 当队列为空时,它会阻塞访问线程 当队列为空时返回 null,它不会阻塞访问线程

    6. 总结才能进步

    首先,我们分别讨论了这两种队列实现及其一些特性、相似性、以及它们之间的差异。这样的比较,是否让你对这两种队列有了更深刻的印象?

    公众号: 锅外的大佬 ,欢迎加群~
    博客地址: http://www.developlee.top

  • 相关阅读:
    移动硬盘插入电脑后检测到却不显示如何解决?
    Js求角度、三角形、弧度计算
    Python之常用模块(一)
    PostgreSQL中的MVCC 事务隔离
    520了,用32做个简单的小程序
    “TensorFlow 开发者出道计划”全攻略,玩转社区看这里!
    MySQL 返回未包含在group by中的列
    《Mysql日志备份/恢复》大型详细攻略两万字图解(史上最详细,多图)
    年轻就该多尝试,教你20小时Get一项新技能
    Flutter开发指南之理论篇:Dart语法05(单线程模型,事件循环模型,Isolate)
  • 原文地址:https://www.cnblogs.com/liululee/p/13529141.html
Copyright © 2011-2022 走看看