ArrayBlockingQueue源码解析

ArrayBlockingQueue是一个阻塞式的队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口。底层以数组的形式保存数据(实际上可看作一个循环数组)。常用的操作包括 add ,offer,put,remove,poll,take,peek。

一、类声明

  1. public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable

1)AbstractQueue提供了Queue接口的默认实现。

2)BlockingQueue接口定义了阻塞队列必须实现的方法。

3)通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。序列化接口没有方法或字段,仅用于标识可序列化的语义。

二、成员变量

  1. private final E[] items;//底层数据结构
  2.  
  3. private int takeIndex;//用来为下一个take/poll/remove的索引(出队)
  4.  
  5. private int putIndex;//用来为下一个put/offer/add的索引(入队)
  6.  
  7. private int count;//队列中元素的个数
  8.  
  9. private final ReentrantLock lock;//锁
  10.  
  11. private final Condition notEmpty;//等待出队的条件
  12.  
  13. private final Condition notFull;//等待入队的条件

三、构造方法

ArrayBlockingQueue提供了两个构造方法:

  1. /**
  2. * 创造一个队列,指定队列容量,指定模式
  3. * @param fair
  4. * true:先来的线程先操作
  5. * false:顺序随机
  6. */
  7. public ArrayBlockingQueue(int capacity, boolean fair) {
  8. if (capacity <= 0)
  9. throw new IllegalArgumentException();
  10. this.items = (E[]) new Object[capacity];//初始化类变量数组items
  11. lock = new ReentrantLock(fair);//初始化类变量锁lock
  12. notEmpty = lock.newCondition();//初始化类变量notEmpty Condition
  13. notFull = lock.newCondition();//初始化类变量notFull Condition
  14. }
  15.  
  16. /**
  17. * 创造一个队列,指定队列容量,默认模式为非公平模式
  18. * @param capacity <1会抛异常
  19. */
  20. public ArrayBlockingQueue(int capacity) {
  21. this(capacity, false);
  22. }

ArrayBlockingQueue的组成:一个对象数组+1把锁ReentrantLock+2个条件Condition

三、成员方法

  • 入队方法

  ArrayBlockingQueue的添加数据方法有add,put,offer这3个方法,总结如下:

  add方法内部调用offer方法,如果队列满了,抛出IllegalStateException异常,否则返回true

  offer方法如果队列满了,返回false,否则返回true

  add方法和offer方法不会阻塞线程,put方法如果队列满了会阻塞线程,直到有线程消费了队列里的数据才有可能被唤醒。

  这3个方法内部都会使用可重入锁保证原子性。

1)add方法:

  1. public boolean add(E e) {
  2. if (offer(e))
  3. return true;
  4. else
  5. throw new IllegalStateException("Queue full");
  6. }

2)offer方法:

在队尾插入一个元素, 如果队列没满,立即返回true; 如果队列满了,立即返回false。因为使用的是ReentrantLock重入锁,所以需要显式地加锁和释放锁。

  1. public boolean offer(E e) {
  2. if (e == null)
  3. throw new NullPointerException();
  4. final ReentrantLock lock = this.lock;
  5. lock.lock();
  6. try {
  7. if (count == items.length)//数组满了
  8. return false;
  9. else {//数组没满
  10. insert(e);//插入一个元素
  11. return true;
  12. }
  13. } finally {
  14. lock.unlock();
  15. }
  16. }

在插入元素结束后,唤醒等待notEmpty条件(即获取元素)的线程。

  1. /**
  2. * 在队尾插入一个元素,并设置了超时等待的时间
  3. * 如果数组已满,则进入等待,直到出现以下三种情况:
  4. * 1、被唤醒
  5. * 2、等待时间超时
  6. * 3、当前线程被中断
  7. */
  8. public boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException {
  9.   if (e == null)
  10.     throw new NullPointerException();
  11.   long nanos = unit.toNanos(timeout);//将超时时间转换为纳秒
  12.   final ReentrantLock lock = this.lock;
  13. /*
  14. * lockInterruptibly():
  15. * 1、 在当前线程没有被中断的情况下获取锁。
  16. * 2、如果获取成功,方法结束。
  17. * 3、如果锁无法获取,当前线程被阻塞,直到下面情况发生:
  18. * 1)当前线程(被唤醒后)成功获取锁
  19. * 2)当前线程被其他线程中断
  20. *
  21. * lock()
  22. * 获取锁,如果锁无法获取,当前线程被阻塞,直到锁可以获取并获取成功为止。
  23. */
  24.   lock.lockInterruptibly();//加可中断的锁
  25.   try {
  26.     for (;;) {
  27.       if (count != items.length) {//队列未满
  28.         insert(e);
  29.         return true;
  30.       }
  31.       if (nanos <= 0)//已超时
  32.         return false;
  33.       try {
  34.         /*
  35.         * 进行等待:
  36.         * 在这个过程中可能发生三件事:
  37.         * 1、被唤醒-->继续当前这个for(;;)循环
  38.         * 2、超时-->继续当前这个for(;;)循环
  39.         * 3、被中断-->之后直接执行catch部分的代码
  40.         */
  41.         nanos = notFull.awaitNanos(nanos);//进行等待(在此过程中,时间会流失,在此过程中,线程也可能被唤醒)
  42.       } catch (InterruptedException ie) {//在等待的过程中线程被中断
  43.         notFull.signal(); // 唤醒其他未被中断的线程
  44.         throw ie;
  45.       }
  46.     }
  47.   } finally {
  48.     lock.unlock();
  49.   }
  50. }

无论是第一个offer方法还是第二个offer方法都调用了insert方法,insert方法的步骤是首先添加元素,然后利用inc函数进行索引的添加,最后会唤醒因为队列中没有数据而等待被阻塞的获取数据的方法。

  1. private void insert(E x) {
  2. items[putIndex] = x; // 元素添加到数组里
  3. putIndex = inc(putIndex); // 放数据索引+1,当索引满了变成0
  4. ++count; // 元素个数+1
  5. notEmpty.signal(); // 使用条件对象notEmpty通知,比如使用take方法的时候队列里没有数据,被阻塞。这个时候队列insert了一条数据,需要调用signal进行通知
  6. }

其中inc函数来改变索引的增加:

  1. final int inc(int i) {
  2. return (++i == items.length) ? 0 : I;
  3. }

3)put方法

  1. /**
  2. * 在队尾插入一个元素
  3. * 如果队列满了,一直阻塞,直到数组不满了或者线程被中断
  4. */
  5. public void put(E e) throws InterruptedException {
  6.   if (e == null)
  7.     throw new NullPointerException();
  8.   final E[] items = this.items;
  9.   final ReentrantLock lock = this.lock;
  10.   lock.lockInterruptibly();
  11.   try {
  12.     try {
  13.       while (count == items.length)//队列满了,一直阻塞在这里
  14.         /*
  15.         * 一直等待条件notFull,即被其他线程唤醒
  16.         * (唤醒其实就是,有线程将一个元素出队了,然后调用notFull.signal()唤醒其他等待这个条件的线程,同时队列也不慢了)
  17.        */
  18.         notFull.await();
  19.     } catch (InterruptedException ie) {//如果被中断
  20.       notFull.signal(); // 唤醒其他等待该条件(notFull,即入队)的线程
  21.       throw ie;
  22.     }
  23.     insert(e);
  24.   } finally {
  25.     lock.unlock();
  26.   }
  27. }
  • 出队方法

ArrayBlockingQueue有不同的几个数据删除方法,poll、take、remove方法。

ArrayBlockingQueue的删除数据方法有poll,take,remove这3个方法,总结如下:

poll方法对于队列为空的情况,返回null,否则返回队列头部元素。

remove方法取的元素是基于对象的下标值,删除成功返回true,否则返回false。

poll方法和remove方法不会阻塞线程。

take方法对于队列为空的情况,会阻塞并挂起当前线程,直到有数据加入到队列中。

这3个方法内部都会调用notFull.signal方法通知正在等待队列满情况下的阻塞线程。

1)poll方法

  1. public E poll() {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock(); // 加锁,保证调用poll方法的时候只有1个线程
  4. try {
  5. return (count == 0) ? null : extract(); // 如果队列里没元素了,返回null,否则调用extract方法
  6. } finally {
  7. lock.unlock(); // 释放锁,让其他线程可以调用poll方法
  8. }
  9. }

poll方法内部调用extract方法:

  1. private E extract() {
  2.   final E[] items = this.items;
  3.   E x = items[takeIndex];//获取出队元素
  4.   items[takeIndex] = null;//将出队元素位置置空
  5.   /*
  6.   * 第一次出队的元素takeIndex==0,第二次出队的元素takeIndex==1
  7.   * (注意:这里出队之后,并没有将后面的数组元素向前移)
  8.   */
  9.   takeIndex = inc(takeIndex);
  10.   --count;//数组元素个数-1
  11.   notFull.signal();//数组已经不满了,唤醒其他等待notFull条件的线程
  12.   return x;//返回出队的元素
  13. }

同样地notfull标志表示数组已经不满,可以执行被阻塞的入队操作。

2)take方法

  1. public E take() throws InterruptedException {
  2. final ReentrantLock lock = this.lock;
  3. lock.lockInterruptibly(); // 加锁,保证调用take方法的时候只有1个线程
  4. try {
  5. while (count == 0) // 如果队列空,阻塞当前线程,并加入到条件对象notEmpty的等待队列里
  6. notEmpty.await(); // 线程阻塞并被挂起,同时释放锁
  7. return extract(); // 调用extract方法
  8. } finally {
  9. lock.unlock(); // 释放锁,让其他线程可以调用take方法
  10. }
  11. }

3)remove方法

  1. public boolean remove(Object o) {
  2. if (o == null) return false;
  3. final Object[] items = this.items;
  4. final ReentrantLock lock = this.lock;
  5. lock.lock(); // 加锁,保证调用remove方法的时候只有1个线程
  6. try {
  7. for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) { // 遍历元素
  8. if (o.equals(items[i])) { // 两个对象相等的话
  9. removeAt(i); // 调用removeAt方法
  10. return true; // 删除成功,返回true
  11. }
  12. }
  13. return false; // 删除成功,返回false
  14. } finally {
  15. lock.unlock(); // 释放锁,让其他线程可以调用remove方法
  16. }
  17. }

以及

  1. void removeAt(int i) {
  2. final Object[] items = this.items;
  3. if (i == takeIndex) { // 如果要删除数据的索引是取索引位置,直接删除取索引位置上的数据,然后取索引+1即可
  4. items[takeIndex] = null;
  5. takeIndex = inc(takeIndex);
  6. } else { // 如果要删除数据的索引不是取索引位置,移动元素元素,更新取索引和放索引的值
  7. for (;;) {
  8. int nexti = inc(i);
  9. if (nexti != putIndex) {
  10. items[i] = items[nexti];
  11. i = nexti;
  12. } else {
  13. items[i] = null;
  14. putIndex = i;
  15. break;
  16. }
  17. }
  18. }
  19. --count; // 元素个数-1
  20. notFull.signal(); // 使用条件对象notFull通知,比如使用put方法放数据的时候队列已满,被阻塞。这个时候消费了一条数据,队列没满了,就需要调用signal进行通知
  21. }

Java源码解析——集合框架(二)——ArrayBlockingQueue的更多相关文章

  1. Java源码解析——集合框架(三)——Vector

    Vector源码解析 首先说一下Vector和ArrayList的区别: (1) Vector的所有方法都是有synchronized关键字的,即每一个方法都是同步的,所以在使用起来效率会非常低,但是 ...

  2. Java源码解析——集合框架(一)——ArrayList

    ArrayList源码分析 ArrayList就是动态数组,是Array的复杂版本,它提供了动态的增加和减少元素.灵活的设置数组的大小. 一.类声明 public class ArrayList< ...

  3. Java源码解析——集合框架(五)——HashMap源码分析

    HashMap源码分析 HashMap的底层实现是面试中问到最多的,其原理也更加复杂,涉及的知识也越多,在项目中的使用也最多.因此清晰分析出其底层源码对于深刻理解其实现有重要的意义,jdk1.8之后其 ...

  4. Java源码解析——集合框架(四)——LinkedListLinkedList原码分析

    LinkedList源码分析 LinkedList也和ArrayList一样实现了List接口,但是它执行插入和删除操作时比ArrayList更加高效,因为它是基于链表的.基于链表也决定了它在随机访问 ...

  5. Spring5源码解析-Spring框架中的单例和原型bean

    Spring5源码解析-Spring框架中的单例和原型bean 最近一直有问我单例和原型bean的一些原理性问题,这里就开一篇来说说的 通过Spring中的依赖注入极大方便了我们的开发.在xml通过& ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  7. [java源码解析]对HashMap源码的分析(二)

    上文我们讲了HashMap那骚骚的逻辑结构,这一篇我们来吹吹它的实现思想,也就是算法层面.有兴趣看下或者回顾上一篇HashMap逻辑层面的,可以看下HashMap源码解析(一).使用了哈希表得“拉链法 ...

  8. [Java源码解析] -- String类的compareTo(String otherString)方法的源码解析

    String类下的compareTo(String otherString)方法的源码解析 一. 前言 近日研究了一下String类的一些方法, 通过查看源码, 对一些常用的方法也有了更透彻的认识,  ...

  9. 【Java源码解析】Thread

    简介 线程本质上也是进程.线程机制提供了在同一程序内共享内存地址空间运行的一组线程.对于内核来讲,它就是进程,只是该进程和其他一下进程共享某些资源,比如地址空间.在Java语言里,Thread类封装了 ...

随机推荐

  1. Avro-RPC client in Flume

    Avro used in Flume Define the interface of RpcClient public interface RpcClient { public int getBatc ...

  2. 22_AOP_切面——静态切面

    [Spring AOP 如何定位连接点] 1.增强提供了连接点的方位信息:如织入到方法前面.后面等. 2.切点描述的是织入到哪些类的哪些方法上. [切点] Spring通过org.springfram ...

  3. 【起航计划 035】2015 起航计划 Android APIDemo的魔鬼步伐 34 App->Service->Local Service Controller

    Local Service Controller 是将LocalService当作“Started”Service来使用,相对于”Bound” Service 来说,这种模式用法要简单得多,Local ...

  4. SSM整合的简单实现

    整合需要的jar包和源码将在文末给出 本文参考黑马程序员视频,由于视频用的环境和我使用的环境不同,建议使用我的环境及jar包(比较新) 一 整合思路 第一步 整合dao层 mybatis和spring ...

  5. Flask入门request session cookie(二)

    1 HTTP方法分类 1 GET 浏览器告知服务器:只获取页面上的信息并发给我.这是最常用的方法. 2 HEAD 浏览器告诉服务器:欲获取信息,但是只关心消息头 .应用应像处理 GET 请求一样来处理 ...

  6. oracle_great_integration_译文

    website:https://www.oracle.com/corporate/features/great-integrations.html Great Integrations(伟大的整合) ...

  7. Struts的学习-例子

    一.新建空项目user和配置maven实现下面的页面 1.配置内容 2.编写struts.xml实现页面 <!--定义一个useraction--> <package name=&q ...

  8. Intel® Manager for Lustre* software(一)

    Intel® Manager for Lustre* software Installation 软件安装指导目录: 安装IML(Intel® Manager for Lustre* software ...

  9. Jmeter入门10 jmeter加密串处理方式2:BeanShell PreProcessor

    上一个博客讲了方式一:函数助手__digest加密,BeanShell PreProcessor也可以用java代码进行处理 线程组.参数.请求都直接使用上一个博客的. 第一步 添加BeanShell ...

  10. bash: ./adb: No such file or directory

    运行adb出现这种错误: bash: ./adb: No such file or directory   但adb确实存在. 可能1.你用的是64位的Linux,没装32位运行时库,安装 $ sud ...