java源码阅读之ArrayBlockingQueue

阻塞队列 (BlockingQueue)是Java util.concurrent包下重要的数据结构,BlockingQueue提供了线程安全的队列访问方式:当阻塞队列

进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。

并发包下很多高级同步类的实现都是基于BlockingQueue实现的。

BlockingQueue 的操作方法

BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下: 
这里写图片描述

四组不同的行为方式解释:

  • 抛异常:如果试图的操作无法立即执行,抛一个异常。
  • 特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
  • 阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
  • 超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是true / false)。

无法向一个 BlockingQueue 中插入 null。如果你试图插入 null,BlockingQueue 将会抛出一个 NullPointerException。

可以访问到 BlockingQueue 中的所有元素,而不仅仅是开始和结束的元素。比如说,你将一个对象放入队列之中以等待处理,但你的应用想要将其取消掉。那么你可以调用诸如 remove(o) 方法来将队列之中的特定对象进行移除。但是这么干效率并不高(译者注:基于队列的数据结构,获取除开始或结束位置的其他对象的效率不会太高),因此你尽量不要用这一类的方法,除非你确实不得不那么做。

ArrayBlockingQueue的继承关系:


ArrayBlockingQueue的源码分析:

废话不多讲,我们直接来分析ArrayBlockingQueue的源码

先看构造函数:

  1. public ArrayBlockingQueue(int capacity);  
  2.   
  3. public ArrayBlockingQueue(int capacity, boolean fair) ;  
  4.   
  5. public ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c);  
 ArrayBlockingQueue提供了三个构造函数,可以看出容量是必传的参数,这也导致了 
ArrayBlockingQueue的容量是固定的。fair 表示队列是否是公平的,即入队时先被阻塞的是否先进入队列。 


以下是构造的主要过程:


  1. public ArrayBlockingQueue(int capacity, boolean fair) {    
  2.         if (capacity <= 0)    
  3.             throw new IllegalArgumentException();    
  4.         this.items = new Object[capacity];  //初始化数组  
  5.         lock = new ReentrantLock(fair);  //根据传的参数决定是否使用公平锁  
  6.         notEmpty = lock.newCondition();  //当队列不为空的时候将唤醒此条件  
  7.         notFull =  lock.newCondition();  //当队列容量没有充满的时候唤醒此条件  
  8. }  

以下是主要的属性

  1. final Object[] items;  
  2.   
  3. int takeIndex;  
  4.   
  5. int putIndex;  
  6.   
  7. int count;  
  8.   
  9. /** Main lock guarding all access */  
  10. final ReentrantLock lock;  
  11.   
  12. /** Condition for waiting takes */  
  13. private final Condition notEmpty;//为空时需要阻塞  
  14.   
  15. /** Condition for waiting puts */  
  16. private final Condition notFull;//容量满时需要阻塞  

接下来我们将分析最主要的两个方法:take 和 put

take从队列中拿出一个元素,如果队列为空将会阻塞直到队列不为空并且能够获取到元素

put将一个元素放入队列,如果队列容量已满将会阻塞直到队列容量不满并且能够入队

以下是take的流程:

  1. public E take() throws InterruptedException {  
  2.         final ReentrantLock lock = this.lock;  
  3.         lock.lockInterruptibly();//可以看出是通过lock来响应中断的  
  4.         try {  
  5.             while (count == 0)  
  6.                 notEmpty.await();//如果队列中没有数据,将会一直阻塞。  
  7.             return dequeue();  
  8.         } finally {  
  9.             lock.unlock();  
  10.         }  
  11. }  


  1. /**  
  2.      * 只发生在持有锁的时候 
  3.      * Extracts element at current take position, advances, and signals. 
  4.      * Call only when holding lock. 
  5.      */  
  6.     private E dequeue() {  
  7.         // assert lock.getHoldCount() == 1;  
  8.         // assert items[takeIndex] != null;  
  9.         final Object[] items = this.items;  
  10.         @SuppressWarnings("unchecked")  
  11.         E x = (E) items[takeIndex];  
  12.         items[takeIndex] = null;  
  13.         if (++takeIndex == items.length)  
  14.             takeIndex = 0;  
  15.         count--;  
  16.         if (itrs != null)  
  17.             itrs.elementDequeued();  
  18.         notFull.signal();//通知notFull condition 队列已经有空位了 可以入队了。  
  19.         return x;  
  20.     }  

put操作类似


  1. public void put(E e) throws InterruptedException {  
  2.        checkNotNull(e);  
  3.        final ReentrantLock lock = this.lock;  
  4.        lock.lockInterruptibly();  
  5.        try {  
  6.            while (count == items.length)  
  7.                notFull.await();  
  8.            enqueue(e);  
  9.        } finally {  
  10.            lock.unlock();  
  11.        }  
  12.    }


  1. /** 
  2.      * Inserts element at current put position, advances, and signals. 
  3.      * Call only when holding lock. 
  4.      */  
  5.     private void enqueue(E x) {  
  6.         // assert lock.getHoldCount() == 1;  
  7.         // assert items[putIndex] == null;  
  8.         final Object[] items = this.items;  
  9.         items[putIndex] = x;  
  10.         if (++putIndex == items.length)  
  11.             putIndex = 0;  
  12.         count++;  
  13.         notEmpty.signal();  
  14.     }  

以上便是ArrayBlockingQueue的两个核心方法,可以看出其实现原理比较简单,是一个典型的生产者消费者模式。但是由于其容量大
小固定,同时put和take使用的是同一个锁(没有办法同时进行put和take),造成了其有很大的局限性。固在线程池中没有默认将其作为阻
塞队列。但是对帮助我们理解阻塞队列有很大帮助。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值