浅谈阻塞队列 BlockingQueue

关于阻塞队列,具体写过几篇源码剖析的文章。

  • ArrayBlockingQueue 源码解析
  • LinkedBlockingQueue 源码解析
  • PriorityBlockingQueue 源码解析
  • SynchronousQueue 源码解析
  • DelayQueue 源码解析

常用的这几个阻塞队列,都逐一解析了一遍。

今天做一个大概的总结。

.

  • 线程安全

阻塞队列首先在强调一点,它是线程安全的,也就是支持多并发的。

需要说的要,线程安全,指的是入队出队等等操作,内部用了锁、或者其它的并发控制技术,

比如CAS,再比如延时队列中的 leader 设计。

但是,阻塞队列里提供的集合操作,不是线程安全的,

比如 containsAll removeAll 等等,这些方法想要线程安全,需要自己实现。
.

  • 阻塞

所谓阻塞,大概的意思是:

当队满的情况下,要放入元素,它可以一直等到,队列中有空位时,再让元素放进去。

当队空的情况下,要取出元素,它可以一直等到,队列中有新元素时,再让其取走。

也不是所有的入队/出队 操作都会阻塞,

比如插入元素

add( e) 方法,当没有空位时,会抛出异常

offer(e) 方法,当没有空位时,会返回一个特殊值,比如 一个 boolean 值

put(e) 方法,当没有空位时,就会阻塞

offer(e, time, unit) 方法,当一定时限内没有空位,则会超时

BlockingQueue 中的 API 方法是很丰富的,可根据自己的需要选择不同的方法。
.

  • 不接受 null 值

你不能把 null 插入到 阻塞队列中,会直接抛出 NullPointerException()

这是个前提要求,为啥呢?

因为有些方法,可能返回 null,它是有操作意义的。

比如 poll() 方法,可能会返回 null ,表明队列中没有元素。
.

  • ArrayBlockingQueue

基于数组实现的,初始化时,就确定了数组的大小,也不支持扩容。入队和出队是同一把锁。

也就是说,同一时刻,只能进行其中的一种操作,不可能出队入队同一时刻发生。

内部维护了两个指针,分别是出队的下标,入队的下标。

需要注意一点的是,虽然是一把锁,但 Condition 却是两个 。

    /** Main lock guarding all access */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    private final Condition notFull;
  • LinkedBlockingQueue

基于链表实现,可近似认为容量没限制。

容量没有限制,如果一直有入队操作,会不会内存溢出呀?

采用的是双锁,出队操作对应出队锁,入队操作对应入队锁。

也就是说,同一时刻,出队与入队可并行。
.

  • PriorityBlockingQueue

是特殊的一个阻塞队列,底层用了 PriorityQueue,也就是用了 这种数据结构。

在入队也出队时,高效的实现元素排序,操作的时间复杂度为 O(logn)。

只要对 这种数据结构熟悉,那这个阻塞队列就很容易就理解了。

它的默认初始容量是 11 ,能自动扩容

元素少于 64 时,是一次扩容一倍,否则一次扩容 50%。
.

  • SynchronousQueue

这是个更为特殊的阻塞队列,容量为0。

入队操作和出队操作,同时出现时,才会成功。

即 A 线程的入队操作,碰上 B 线程的出队操作,两者相当于现场传递。

单独的入队,单独的出队,要么是失败,要么是阻塞。

这个阻塞队列的代码实现,是很不好懂,它在 CachedThreadPool 中有被使用。
.

  • DelayQueue

这是一个延时队列,底层用了PriorityQueue 实现排序功能,

实现了 Delayed 这个接口,可以做到到期的对象才能出队。

入队则不受什么限制。

在出队源码中,并发控制的非常好,而且还有一个leader 设计,很是独特。

你可能感兴趣的:(并发编程,BlockingQueue,阻塞队列,offer,take)