Java-并发-队列-阻塞和非阻塞队列总结

Java-并发-队列-阻塞和非阻塞队列总结

转载声明:

本文系转载自以下文章:

0x01 摘要

本文会对java并发包内的常用重要阻塞/非阻塞队列进行总结。

0x02 非阻塞式集合

这类集合也包括添加和移除的方法,如果方法不能立即被执行,则返回null或抛出异常,但是调用这个方法的线程不会被阻塞。

2.1 ConcurrentLinkedQueue

ConcurrentLinkedQueue
基于链接节点的无限制线程安全队列,此队列命令元素FIFO(先进先出)。这个队列在add(),remove(),poll()都用了cas来保证安全。

在iterator()时,如果集合被改变,那么数据可能会不一致。

2.2 ConcurrentLinkedDeque

  • 基于链接节点的无界并发的双端队列。
  • 并发插入,删除和访问操作安全执行。
  • 因为这些deques的异步性质,确定当前的数量,元素需要遍历元素,因此可以报告,如果在遍历期间修改此集合,则结果不准确。
  • 也是使用cas来保证线程安全,这个类不仅可以操控头部,也可以操控尾部。

0x03 阻塞式集合

这类集合包括添加和移除的数据方法。当集合已满或为空时,被调用的添加或者移除方法就不能立即被执行,那么调用这个方法的线程将被阻塞,一直到该方法可以被成功执行。

3.2 ArrayBlockingQueue

ArrayBlockingQueue
ArrayBlockingQueue 特点:

  • 用数组实现
  • 有界阻塞队列
    初始化时需要指定队列的大小,它不会像ArrayList那样自动扩容。
  • FIFO
  • ArrayBlockingQueue 是一种逻辑上的环形队列。
  • 线程安全
    Vectorsynchronized关键字进行线程同步,而ArrayBlockingQueue中通过ReentrantLock来完成的。
  • 入队和出队不可并发
    ArrayBlockingQueue 在入队和出队上都使用了同一个重入锁,因此入队和出队是不能并发执行的。
  • 可重入锁和Condition:
    1. 可重入锁是独占式的锁,来保证对队列的访问都是线程安全的阻塞操作。ReentrantLock 有公平锁和非公平锁之分,因此需要指定,默认是非公平锁。
    2. Condition控制什么情况下该阻塞或不阻塞。

源码分析请参考:Java 并发 — 阻塞队列之ArrayBlockingQueue源码分析

3.3 LinkedBlockingQueue

LinkedBlockingQueue
LinkedBlockingQueue特点如下:

  • 用链表实现
  • 有界阻塞队列
  • FIFO
  • LinkedBlockingQueue 和ArrayBlockingQueue 是差不多的,只是储存方式由数组转换为链表了而已,LinkedBlockingQueue 也是通过重入锁和Condition
    来对队列的操作访问进行控制。
  • 入队和出队可并发
    在ArrayBlockingQueue中,入队和出队都是用的同一个可重入锁,出队入队不可并发;而LinkedBlockingQueue 对于出队和入队使用了不同的可重入锁来控制
  • 对于队列中元素的个数使用了原子类AtomicInteger,来保证对count的操作具有原子性。

自己实现LinkedBlockingQueue:Java-并发-自己实现阻塞队列
源码分析请参考:Java 并发 — 阻塞队列之PriorityBlockingQueuey源码分析

3.4 PriorityBlockingQueue

先看看继承关系:
PriorityBlockingQueue

扫描二维码关注公众号,回复: 4432148 查看本文章

PriorityBlockingQueue 特点:

  • 支持优先级
  • 无界阻塞队列
    PriorityBlockingQueue 是无界队列,不会“队满”。实际当到达队列最大值后(Integer.MAX_VALUE - 8,减8的原因是:数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。),就抛OOM异常了,因此这点在使用优先队列的时候需要注意。
  • 放入队列的元素必须实现Comparable接口的public int compareTo(T o)方法
  • 默认情况下comparatornull。此时会根据元素的compareTo方法来来排序,比如String类型就是按其实现的挨个比较内部char数组的 Ascii码来排序。采取从小到大顺序升序排列,也可以自定义类实现compareTo()方法来指定元素排序规则,需要注意的是不能保证同优先级元素的顺序
  • 线程安全
    通过一个可重入锁ReentrantLock来控制入队和出队操作
  • 入队出队不可并发
  • 通过二叉堆来实现
    默认情况下,父元素的值永远比任何子元素的值还小。也就是说,最小的值是queue[0]

二叉堆 使用的是数组来实现,对一个二叉树进行编号,然后按照顺序存放在数组中。
二叉堆

二叉堆2

观察一下父子节点之间的编号,会发现如果节点在数组中的位置是i(i是节点在数组中的下标), 则i节点对应的子节点在数组中的位置分别是 2i + 12i + 2,同时i的父节点的位置为 (i-1)/2

因此现在我们就把树存储到了数组中,同时通过这种规律可以很快找到每个节点的父亲和孩子节点。
关于二叉堆的更多操作,可以看上面的链接,这里就不赘述了,简单提一下就好。

源码分析请参考:Java-并发-队列-PriorityBlockingQueue

3.5 DelayQueue

  • DelayQueue 内部通过组合PriorityQueue 来实现存储和维护元素顺序的,其存储元素必须实现Delayed 接口。通过实现Delayed 接口,可以获取到元素延迟时间,以及可以比较元素大小(Delayed 继承Comparable)
  • DelayQueue 通过一个可重入锁来控制元素的入队出队行为
  • DelayQueue 中leader 标识 用于减少线程的竞争,表示当前有其它线程正在获取队头元素。
  • PriorityQueue 只是负责存储数据以及维护元素的顺序,对于延迟时间取数据则是在DelayQueue 中进行判断控制的。

源码分析请参考:Java 并发 — 阻塞队列之DelayQueue源码分析

3.6 SynchronousQueue

  • SynchronousQueue 不是一个真正的队列,其主要功能不是存储元素,而且维护一个排队的线程清单。这些线程等待把元素加入或者移出队列。每一个线程的入队(出队)操作必须等待另一个线程的出队(入队)操作。

  • SynchronousQueue中的队列不是针对数据的,而是针对操作,也就是入队不一定就是入队数据,而是入队的操作,操作可以是put,也可以是take,put操作与take操作对应,可以互相匹配,put和put,take和take则是相同的操作(模式)。

  • SynchronousQueue 并没有使用锁来保证线程的安全,使用的是循环CAS方法。
    SynchronousQueue有两种模式:

    1. 公平模式
      所谓公平就是遵循先来先服务的原则,因此其内部使用了一个FIFO队列来实现其功能。
    2. 非公平模式
      SynchronousQueue 中的非公平模式是默认的模式,其内部使用栈来实现其功能,也就是后来的先服务,

源码分析请参考:Java 并发 — 阻塞队列之SynchronousQueue源码分析

3.7 LinkedTransferQueue

  • LinkedTransferQueue 和SynchronousQueue 其实基本是差不多的,两者都是无锁带阻塞功能的队列
  • SynchronousQueue 通过内部类Transferer 来实现公平和非公平队列
  • LinkedTransferQueue 中没有公平与非公平的区分
  • LinkedTransferQueue 实现了TransferQueue接口,该接口定义的是带阻塞操作的操作,相比SynchronousQueue 中的Transferer 功能更丰富。
  • LinkedTransferQueue是基于链表的FIFO无界阻塞队列,它是JDK1.7才添加的阻塞队列,有4种操作模式:
private static final int NOW   = 0; // for untimed poll, tryTransfer
private static final int ASYNC = 1; // for offer, put, add
private static final int SYNC  = 2; // for transfer, take
private static final int TIMED = 3; // for timed poll, tryTransfer
  • NOW :如果在取数据的时候,如果没有数据,则直接返回,无需阻塞等待。
  • ASYNC:入队的操作都不会阻塞,也就是说,入队后线程会立即返回,不需要等到消费者线程来取数据。
  • SYNC :取数据的时候,如果没有数据,则会进行阻塞等待。
  • TIMED : 取数据的时候,如果没有数据,则会进行超时阻塞等待。

源码分析请参考:Java 并发 — 阻塞队列之LinkedTransferQueue源码分析

3.8 LinkedBlockingDeque

  • LinkedBlockingDeque是基于双向链表的双端有界阻塞队列
  • 默认使用非公平ReentrantLock实现线程安全
  • 默认队列最大长度都为Integer.MAX_VALUE
  • 不允许null元素添加
  • 双端队列可以用来实现 “窃取算法” ,两头都可以操作队列,相对于单端队列可以减少一半的竞争。
  • LinkedBlockingDeque 是基于链表的,因此分析它实际就是分析链表而已,这个和我们前面LinkedBlockingQueue实质是差不多的,只是这里是双端队列,可以两头操作队列(队尾也可以出队,队头也可以入队)。

源码分析请参考:Java 并发 — 阻塞队列之LinkedBlockingDeque源码分析

猜你喜欢

转载自blog.csdn.net/baichoufei90/article/details/84405459