一、摘要

  BlockingQueue通常用于一个线程在生产对象,而另外一个线程在消费这些对象的场景,例如在线程池中,当运行的线程数目大于核心的线程数目时候,经常就会把新来的线程对象放到BlockingQueue中去。

二、阻塞队列原理

  原理简单的来讲:就是一个线程往队列里面放,而另外的一个线程从里面取

  当线程持续的产生新对象并放入到队列中,直到队列达到它所能容纳的临界点。注意,队列的容量是有限的,不可能一直往里面插入对象。如果队列到达了临界点时,这个时候再想往队列中插入元素则会产生阻塞,直到另一端从队列中进行消费了,这个时候阻塞现象才不会发生。另外当去消费一个空的队列时候,这个线程也会产生阻塞现象,直到一个线程把对象插入到队列中

三、BlockingQueue常用方法总结

  抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove(e) poll take() poll(time,unit)
检查 element(e) peek 不可用 不可用

  四组不同的行为方式解释:
    1.  抛异常:如果试图的操作无法立即执行,抛一个异常。
    2.  特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
    3.  阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
    4.  超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等
      待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。
      无法向一个 BlockingQueue 中插入 null。如果你试图插入 null,BlockingQueue 将会抛出
      一个 NullPointerException。

四、BlockingQueue源码分析

  1、通过IDE可以明显的看到BlockingQueue是一个接口,我们在写代码的时候需要实现这个接口

    java.util.concurrent 具有以下 BlockingQueue 接口的实现(Java 8):

      

五、数组阻塞队列ArrayBlockingQueue分析

  1、原理分析

    首先ArrayBlockingQueue 类实现了 BlockingQueue 接口。其次ArrayBlockingQueue 是一个有界的阻塞队列,其内部实现是将对象放到一个数组里,所以一旦创建了该队列,就不能再增加其容量了。最后ArrayBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。

  2、ArrayBlockingQueue的方法(下面着重分析put()和take()二者方法)

    此构造方法中,我们能看到传入了两个参数,capacity代表着队列的容量大小,而boolean类型的参数则是判断是否为公平锁,如果为true,则先到的线程会先得到锁对象,    反之则有操作系统去决定哪个线程获得锁,大多数情况下都是设置为false,这样性能会高点

    在put方法中,我们能看到在执行put方法时,我们必须要对其进行加锁操作,从而保证线程的安全性。其次会去判断其队列是否饱满了,饱满时则会发生阻塞现象,直到被其他线程唤醒时插入元素,接着会去调用notEmpty.signal()方法,间接的利用take方法将队列中的元素取走,最后将锁释放。

       同理可以看出take()方法是相反的,不再做详细介绍,代码注释已给出

    put()和take()精简源代码如下:

  /** The queued items */
final Object[] items;      //利用数组来存储元素
  /** Main lock guarding all access */
final ReentrantLock lock; /** Condition for waiting takes */
private final Condition notEmpty; //定义一个Condition对象,用来对take进行操作 /** Condition for waiting puts */
private final Condition notFull;  //定义一个Condition对象,用来对put进行操作 /**
* Creates an {@code ArrayBlockingQueue} with the given (fixed)
* capacity and the specified access policy.
*
* @param capacity the capacity of this queue
* @param fair if {@code true} then queue accesses for threads blocked
* on insertion or removal, are processed in FIFO order;
* if {@code false} the access order is unspecified.
* @throws IllegalArgumentException if {@code capacity < 1}
*/
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0) //判断初始化的容量大小
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
====================================put()方法==============================================
  /**
* Inserts the specified element at the tail of this queue, waiting
* for space to become available if the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();  //队列饱满时,将使这个线程进入阻塞状态,直到被其他线程唤醒时插入元素
enqueue(e);    //将元素插入到队列中
} finally {
lock.unlock();
}
}   /**
* Inserts element at current put position, advances, and signals.
* Call only when holding lock.
*/
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal(); //通知take那边消费其元素
}
 
===================================================take()方法========================================================
 
 public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;  //加锁
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();  //队列为空时,将使这个线程进入阻塞状态,直到被其他线程唤醒时取出元素
return dequeue();  //消费对头中的元素
} finally {
lock.unlock();
}
}   /**
* Extracts element at current take position, advances, and signals.
* Call only when holding lock.
*/
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();  //通知put那边消费其元素
return x;
}

六、链式阻塞队列LinkedBlockingQueue分析

  1、原理分析

    LinkedBlockingQueue 类实现了 BlockingQueue 接口。同时LinkedBlockingQueue 内部以一个链式结构(链接节点)对其元素进行存储。如果需要的话,这一链式结构可以选择一个上限。如果没有定义上限,将使用 Integer.MAX_VALUE 作为上限。LinkedBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。

  2、LinkedBlockingQueue方法分析

    针对LinkedBlockingQueue的构造方法中,我们能看到没有定义上限时,会使用Integer.MAX_VALUE 作为上限

    其次针对put等方法时,原理与ArrayBlockingQueue大致相同,只不过是基于链表去实现的

    源码精简如下:

    

  /** The capacity bound, or Integer.MAX_VALUE if none */
//链表的容量
private final int capacity; /** Current number of elements */
//当前元素个数
private final AtomicInteger count = new AtomicInteger(); /**
* Head of linked list.
* Invariant: head.item == null
*/
//链表头节点
transient Node<E> head; /**
* Tail of linked list.
* Invariant: last.next == null
*/
//链表尾节点
private transient Node<E> last; /** Lock held by take, poll, etc */
//出队列锁
private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, etc */
//入队列锁
private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition(); //默认构造方法,默认执行容量上限
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
} //指定队列的容量
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
//初始化头尾节点的值,设置均为null
last = head = new Node<E>(null);
} //往对尾中插入元素,队列满时,则会发生阻塞,直到有元素消费了或者线程中断了
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;//入队列锁
final AtomicInteger count = this.count;//获取当前队列中的元素个数
putLock.lockInterruptibly();
try {
while (count.get() == capacity) { //条件:如果队列满了
notFull.await(); //则加入到出队列等待中,直到队列不满了,这时就会被其他线程notFull.signal()唤醒
}
enqueue(node);//将元素入队列
c = count.getAndIncrement(); //对当前队列元素个数加1
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
} //出队列,大致原理与入队列相反,当队列为空时,则会阻塞,直到队列不为空或者线程中断
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}

七、ArrayBlockingQueue和LinkedBlockingQueue源码比较

  在上述源码过程我们能发现:

  1、入队列时,当队列满了,则会发生阻塞,直到队列消费了数据或者线程被中断了才会唤醒

  2、出队列时,当队列为空时,则会发生阻塞,直到队列中有数据了或者线程被中断了才会唤醒

  源码注意:

    ArrayBlockingQueue源码中,共用的是同一把锁

    LinkedBlockingQueue源码中,则是用到了两把锁,一把是入队列锁,另一把是出队列锁

八、参考资料

  https://www.cnblogs.com/java-zhao/p/5135958.html

java并发包——阻塞队列BlockingQueue及源码分析的更多相关文章

  1. JAVA并发(7)-并发队列PriorityBlockingQueue的源码分析

    本文讲PriorityBlockingQueue(优先阻塞队列) 1. 介绍 一个无界的具有优先级的阻塞队列,使用跟PriorityQueue相同的顺序规则,默认顺序是自然顺序(从小到大).若传入的对 ...

  2. Java并发编程-阻塞队列(BlockingQueue)的实现原理

    背景:总结JUC下面的阻塞队列的实现,很方便写生产者消费者模式. 常用操作方法 常用的实现类 ArrayBlockingQueue DelayQueue LinkedBlockingQueue Pri ...

  3. 延迟队列DelayQueue take() 源码分析

    延迟队列DelayQueue take() 源码分析 在工作中使用了延迟队列,对其内部的实现很好奇,于是就研究了一下其运行原理,在这里就介绍一下take()方法的源码 1 take()源码 如下所示 ...

  4. Java的三种代理模式&完整源码分析

    Java的三种代理模式&完整源码分析 参考资料: 博客园-Java的三种代理模式 简书-JDK动态代理-超详细源码分析 [博客园-WeakCache缓存的实现机制](https://www.c ...

  5. java 日志体系(四)log4j 源码分析

    java 日志体系(四)log4j 源码分析 logback.log4j2.jul 都是在 log4j 的基础上扩展的,其实现的逻辑都差不多,下面以 log4j 为例剖析一下日志框架的基本组件. 一. ...

  6. java中的==、equals()、hashCode()源码分析(转载)

    在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. ==  java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: ...

  7. Java SPI机制实战详解及源码分析

    背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...

  8. Java并发编程——阻塞队列BlockingQueue

    Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...

  9. 【Java并发编程】16、ReentrantReadWriteLock源码分析

    一.前言 在分析了锁框架的其他类之后,下面进入锁框架中最后一个类ReentrantReadWriteLock的分析,它表示可重入读写锁,ReentrantReadWriteLock中包含了两种锁,读锁 ...

随机推荐

  1. java中equals和==以及toString

    我们通常在控制台输出时使用System.out.print(),print只能直接输出基本类型和字符串,对于其他的类型直接输出将会输出@开头的引用,因此若需要输出对应的内容则需要使用toSring方法 ...

  2. VS2005、vs2008+WinXPDDK+DDKWizard配置驱动开发环境

    所需软件下载地址如下(均为有效资源链接,速度都比较可以): vs2005:    http://221.224.22.210/downloadsawyer/VS.Net2005简体中文版.rar wi ...

  3. 芝麻HTTP:PhantomJS的安装

    PhantomJS是一个无界面的.可脚本编程的WebKit浏览器引擎,它原生支持多种Web标准:DOM操作.CSS选择器.JSON.Canvas以及SVG. Selenium支持PhantomJS,这 ...

  4. 决策树系列(三)——ID3

    预备知识:决策树 初识ID3 回顾决策树的基本知识,其构建过程主要有下述三个重要的问题: (1)数据是怎么分裂的 (2)如何选择分类的属性 (3)什么时候停止分裂 从上述三个问题出发,以实际的例子对I ...

  5. 关于省市联动的bug

    一,问题描述 1.1,原来的思路 1.1.1,初始化加载省份 $.ajax({ 'type' : 'POST', 'dataType' : 'json', 'url' : '${rc.contextP ...

  6. 为Android Studio中的SettingsActivity定制任务栏

    Android Studio为开发者提供了很多内建的Activity, 其中Settings Activity是非常有用且功能强大的一种内建Activity. Setting Activity其实本质 ...

  7. LCT总结(LCT,Splay)

    概念.性质简述 LCT(Link-Cut Tree),就是动态树的一种,用来维护一片森林的信息,滋磁的操作可多啦! 动态连边.删边 合并两棵树.分离一棵树(跟上面不是一毛一样吗) 动态维护连通性 维护 ...

  8. 【BZOJ3529】数表(莫比乌斯反演,树状数组)

    [BZOJ3529]数表(莫比乌斯反演,树状数组) 题解 首先不管\(A\)的范围的限制 要求的东西是 \[\sum_{i=1}^n\sum_{j=1}^m\sigma(gcd(i,j))\] 其中\ ...

  9. BZOJ第1页养成计划

    嗯,用这篇博客当一个目录,方便自己和学弟(妹?)们查阅.不定期更新. BZOJ1000   BZOJ1001   BZOJ1002   BZOJ1003   BZOJ1004   BZOJ1005   ...

  10. 解决Android Studio 3.0导入module依赖后unable to merge index

    解决Android Studio 3.0导入module依赖后unable to merge index 项目需要使用im, 在项目里导入了腾讯im的几个module依赖, 项目无法编译, 报错una ...