zoukankan      html  css  js  c++  java
  • J.U.C队列

    一个线程安全的队列有两种方式:阻塞和非阻塞:

    1.非阻塞队列ConcurrentLinkedQueue

      ConcurrentLinkedQueue是一个基于链接节点的无边界的线程安全队列,遵循队列的FIFO原则,队尾入队,队首出队。采用CAS算法来实现的。

    2.阻塞队列BlockingQueue

      被阻塞的情况主要有如下两种:

    1. 当队列满了的时候进行入队列操作
    2. 当队列空了的时候进行出队列操作

      BlockingQueue 对插入操作、移除操作、获取元素操作提供了四种不同的方法用于不同的场景中使用:

    1. 抛出异常
    2. 返回特殊值(null 或 true/false,取决于具体的操作)
    3. 阻塞等待此操作,直到这个操作成功
    4. 阻塞等待此操作,直到成功或者超时指定时间。总结如下:

       接下来我们介绍这个接口的几个实现类。

    2.1 ArrayBlockingQueue

      ArrayBlockingQueue是一个由数组实现的有界阻塞队列。

      ArrayBlockingQueue内部使用可重入锁ReentrantLock + Condition来完成多线程环境的并发操作。

    • items,一个定长数组,维护ArrayBlockingQueue的元素
    • takeIndex,int,为ArrayBlockingQueue队首位置
    • putIndex,int,ArrayBlockingQueue队尾位置
    • count,元素个数
    • lock,锁,ArrayBlockingQueue出列入列都必须获取该锁,两个步骤公用一个锁

    2.2 LinkedBlockingQueue

    LinkedBlockingQueue和ArrayBlockingQueue的使用方式基本一样,但还是有一定的区别:

    1. 队列的数据结构不同

      ArrayBlockingQueue是一个由数组支持的有界阻塞队列

      LinkedBlockingQueue是一个基于链表的有界(可设置)阻塞队列

    2. 队列中锁的实现不同

    ArrayBlockingQueue实现的队列中的锁是没有分离的,即生产和消费用的是同一个锁;

    LinkedBlockingQueue实现的队列中的锁是分离的,即生产用的是putLock,消费是takeLock

    1. 在生产或消费时操作不同

    ArrayBlockingQueue实现的队列中在生产和消费的时候,是直接将枚举对象插入或移除的;

    LinkedBlockingQueue实现的队列中在生产和消费的时候,需要把枚举对象转换为Node进行插入或移除,会影响性能

    1. 队列大小初始化方式不同

    ArrayBlockingQueue实现的队列中必须指定队列的大小;

    LinkedBlockingQueue实现的队列中可以不指定队列的大小,但是默认是Integer.MAX_VALUE

    2.3 PriorityBlockingQueue

    PriorityBlockingQueue类似于ArrayBlockingQueue内部使用一个独占锁来控制,同时只有一个线程可以进行入队和出队。

    PriorityBlockingQueue是一个优先级队列,它在java.util.PriorityQueue的基础上提供了可阻塞的读取操作。它是无界的,但可能会导致内存溢出而失败。

      PriorityBlockingQueue始终保证出队的元素是优先级最高的元素,并且可以定制优先级的规则,内部使用二叉堆,通过使用一个二叉树最小堆算法来维护内部数组,这个数组是可扩容的,当当前元素个数>=最大容量时候会通过算法扩容。值得注意的是为了避免在扩容操作时候其他线程不能进行出队操作,实现上使用了先释放锁,然后通过CAS保证同时只有一个线程可以扩容成功。

    小结:

    1、优先队列不允许空值,而且不支持non-comparable(不可比较)的对象,比如用户自定义的类。优先队列要求使用Java Comparable和Comparator接口给对象排序,并且在排序时会按照优先级处理其中的元素。

    2、优先队列的头是基于自然排序或者Comparator排序的最小元素。如果有多个对象拥有同样的排序,那么就可能随机地取其中任意一个。也可以通过提供的Comparator(比较器)在队列实现自定的排序。当我们获取队列时,返回队列的头对象。

    3、优先队列的大小是不受限制的,但在创建时可以指定初始大小,当我们向优先队列增加元素的时候,队列大小会自动增加。

    4、PriorityQueue是非线程安全的,所以Java提供了PriorityBlockingQueue(实现BlockingQueue接口)用于Java多线程环境。

    2.4 SynchronousQueue

    SynchronousQueue,实际上它不是一个真正的队列,因为它不会为队列中元素维护存储空间。与其他队列不同的是,它维护一组线程,这些线程在等待着把元素加入或移出队列。SynchronousQueue没有存储功能,因此put和take会一直阻塞,直到有另一个线程已经准备好参与到交付过程中。

    仅当有足够多的消费者,并且总是有一个消费者准备好获取交付的工作时,才适合使用同步队列。这种实现队列的方式看似很奇怪,但由于可以直接交付工作,从而降低了将数据从生产者移动到消费者的延迟。

    直接交付方式还会将更多关于任务状态的信息反馈给生产者。当交付被接受时,它就知道消费者已经得到了任务,而不是简单地把任务放入一个队列——这种区别就好比将文件直接交给同事,还是将文件放到她的邮箱中并希望她能尽快拿到文件。

    SynchronousQueue对于正在等待的生产者和使用者线程而言,默认是非公平排序,也可以选择公平排序策略。但是,使用公平所构造的队列可保证线程以 FIFO 的顺序进行访问。 公平通常会降低吞吐量,但是可以减小可变性并避免得不到服务。

    SynchronousQueue特点:

    • 是一种阻塞队列,其中每个 put 必须等待一个 take,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。

    • 是线程安全的,是阻塞的。

    • 不允许使用 null 元素。

    • 公平排序策略是指调用put的线程之间,或take的线程之间的线程以 FIFO 的顺序进行访问。

    • SynchronousQueue的方法:

      • iterator(): 永远返回空,因为里面没东西。
      • peek() :永远返回null。
      • put() :往queue放进去一个element以后就一直wait直到有其他thread进来把这个element取走。
      • offer() :往queue里放一个element后立即返回,如果碰巧这个element被另一个thread取走了,offer方法返回true,认为offer成功;否则返回false。
      • offer(2000, TimeUnit.SECONDS) :往queue里放一个element但等待时间后才返回,和offer()方法一样。
      • take() :取出并且remove掉queue里的element,取不到东西他会一直等。
      • poll() :取出并且remove掉queue里的element,方法立即能取到东西返回。否则立即返回null。
      • poll(2000, TimeUnit.SECONDS) :等待时间后再取,并且remove掉queue里的element,
      • isEmpty():永远是true。
      • remainingCapacity() :永远是0。
      • remove()和removeAll() :永远是false。
  • 相关阅读:
    什么是“泛在电力物联网”?要建一个什么样的泛在电力物联网?
    基于混合云雾计算的物联网架构
    探索 | “中医+AI”会诊电力设备故障
    泛在电力物联网有项核心技术 你听过没有?
    国网做泛在电力物联网的初衷是什么?如何参与?
    泛在电力物联网技术及战略解读:一个战略 两个领域 三个阶段
    构建“泛在电力物联网”成为国网当前最紧迫、最重要的任务
    如何解决分布式日志exceptionless的写入瓶颈
    SQL 查找是否"存在",别再 count 了,很耗费时间的
    abp vnext 微服务-基于Exceptionless实现分布式日志记录
  • 原文地址:https://www.cnblogs.com/zhuyapeng/p/14354351.html
Copyright © 2011-2022 走看看