ArrayBlockingQueue源码分析

1、ArrayBlockingQueue使用

  ArrayBlockingQueue的使用案例详情如下:

 1 import java.util.concurrent.ArrayBlockingQueue;
 2 
 3 public class TestArrayBlockingQueue {
 4 
 5     public static void main(String[] args) throws Exception {
 6         ArrayBlockingQueue queue = new ArrayBlockingQueue<>(16);
 7         // ============================== poll 、peek、 take 区别 ===============================
 8         // 检索并移除队列头部元素,若没有元素,返回null;有元素,移除元素并返回
 9         Object pollElement = queue.poll();
10         System.out.println("pollElement = " + pollElement);
11         // 检索并移除队列头部元素,队列中没有元素,阻塞等待,知道队列中有元素;有元素,移除元素并返回
12 //        Object takeElement = queue.take();
13 //        System.out.println("Element = " + takeElement);
14 
15         queue.add("123");
16         // 检索队列头部元素,不移除元素
17         Object peekElement = queue.peek();
18         System.out.println("peekElement = " + peekElement);
19         System.out.println("peek after " + queue);
20 
21         // 检索并移除元素
22         pollElement = queue.poll();
23         System.out.println("pollElement = " + pollElement);
24         System.out.println("poll after " + queue);
25 
26         // 检索并移除元素
27         queue.add("234");
28         Object takeElement = queue.take();
29         System.out.println("takeElement = " + takeElement);
30         System.out.println("take after " + queue);
31 
32         // ============================== add 和 offer 区别 ===============================
33         // add 添加成功,返回true;队列满了,抛出异常
34         queue.add("345");
35         // offer 添加成功,返回true;队列满了,返回false
36         queue.offer("456");
37         System.out.println("add 、offer after" + queue);
38 
39 //        for (int i = 0; i < 16; i++) {
40 //            boolean addRes = queue.add(i);
41 //            System.out.println(addRes);
42 //        }
43 //        System.out.println("add after " + queue);
44 
45         for (int i = 0; i < 16; i++) {
46             boolean offerRes = queue.offer(i);
47             System.out.println(offerRes);
48         }
49         System.out.println("offer after " + queue);
50 
51         // 阻塞添加,若队列已满,阻塞队列元素被取出放入
52         queue.put("12346");
53         System.out.println(queue);
54     }
55 }

2、ArrayBlockingQueue继承体系

  ArrayBlockingQueue类继承图:

  

   ArrayBlockingQueue实现了Queue、BlockingQueue接口,继承AbstractCollection类。

2.1、Queue接口

  Queue接口详情:

  

   核心方法:

 1 // 队列接口
 2 public interface Queue<E> extends Collection<E> {
 3    /**
 4      * 添加元素到队列中,添加成功返回true;若队列元素已满,再添加元素,抛出异常
 5      */
 6     boolean add(E e);
 7 
 8     /**
 9      *  添加元素到队列中,添加成功返回true;若队列元素已满,再添加元素,返回false
10      */
11     boolean offer(E e);
12 
13     /**
14      * 检索并移除队列头部元素
15      */
16     E remove();
17 
18     /**
19      * 检索并移除队列头部元素,若队列为空,返回null
20      */
21     E poll();
22 
23     /**
24      * 仅检索队列头部元素,不做移除,若队列为空,抛出异常
25      */
26     E element();
27 
28     /**
29      * 仅检索队列头部元素,不做移除,队列为空返回null
30      */
31     E peek();
32 }

2.2、BlockingQueue接口

  BlockingQueue接口详情:

  

   核心方法:

 1 public interface BlockingQueue<E> extends Queue<E> {
 2     
 3     // ...
 4     
 5     /**
 6      * 添加元素到队列中,若队列已满,阻塞等待队列中有可用的空间
 7      */
 8     void put(E e) throws InterruptedException;
 9 
10     /**
11      * 添加元素到队列中,若队列已满,阻塞等待一定时间,若在指定时间内队列中无可用的空间,返回false
12      */
13     boolean offer(E e, long timeout, TimeUnit unit)
14         throws InterruptedException;
15         
16     /**
17      * 检索并移除队列头元素,若队列为空,阻塞等待,直到队列中有元素可被取出
18      */
19     E take() throws InterruptedException;
20 
21     /**
22       * 检索并移除队列头元素,若队列为空,阻塞等待指定时间,若在指定时间内队列中无可取的元素,返回null
23       */
24     E poll(long timeout, TimeUnit unit)
25         throws InterruptedException;
26 
27    // ...
28 }

  相较Queue,BlockingQueue多了阻塞添加、获取元素的方法。

2.3、AbastrctQueue抽象类

  AbastrctQueue抽象类详情:

  

  核心方法:

 1 public abstract class AbstractQueue<E> extends AbstractCollection<E> implements Queue<E> {
 2 
 3     protected AbstractQueue() {
 4     }
 5 
 6     /**
 7      * 内部由offer实现
 8      * 添加元素到队列中,添加成功返回true;若队列已满,抛出 IllegalStateException 异常
 9      */
10     public boolean add(E e) {
11         if (offer(e))
12             return true;
13         else
14             throw new IllegalStateException("Queue full");
15     }
16 
17     /**
18      * 检索并移除队列头元素,poll方法实现
19      *  与poll方法的区别:队列中元素空,通过poll()获取队列元素,返回null;通过remove()获取队列元素,抛出 NoSuchElementException 异常
20      */
21     public E remove() {
22         E x = poll();
23         if (x != null)
24             return x;
25         else
26             throw new NoSuchElementException();
27     }
28 
29      /**
30      * 检索队列头元素,不会移除元素,peek方法实现
31      *  与peek方法的区别:队列中元素空,通过peek()获取队列元素,返回null;通过element()获取队列元素,抛出 NoSuchElementException 异常
32      */
33     public E element() {
34         E x = peek();
35         if (x != null)
36             return x;
37         else
38             throw new NoSuchElementException();
39     }
40 
41     /**
42      * 清空队列中所有元素, poll方法实现
43      */
44     public void clear() {
45         while (poll() != null)
46             ;
47     }
48 
49     /**
50      * 将指定集合中的所有元素添加到此队列中,add方法实现
51      */
52     public boolean addAll(Collection<? extends E> c) {
53         if (c == null)
54             throw new NullPointerException();
55         if (c == this)
56             throw new IllegalArgumentException();
57         boolean modified = false;
58         for (E e : c)
59             if (add(e))
60                 modified = true;
61         return modified;
62     }
63 }

3、ArrayBlockingQueue源码分析

  ArrayBlockingQueue的成员变量:

 1 // ReentrantLock 可重入锁
 2 final ReentrantLock lock;
 3 
 4 // 阻塞获取数据 (take),用于线程挂起 和 线程唤醒。数组为空,挂起线程阻塞;数组有元素,唤醒线程取数据
 5 private final Condition notEmpty;
 6 
 7 // 阻塞添加数据 (put),用于线程挂起 和 线程唤醒。数组满了,挂起线程阻塞;数组为空,唤醒线程加数据
 8 private final Condition notFull;
 9 
10 // 数组中元素数量
11 int count;
12 
13 // 存储队列元素的数组
14 final Object[] items;
15 
16 // 获取数据的下标
17 int takeIndex;
18 
19 // 存储数据的下标
20 int putIndex;

  ArrayBlockingQueue构造函数:

 1 // capacity:队列容量
 2 public ArrayBlockingQueue(int capacity) {
 3     this(capacity, false);
 4 }
 5 
 6 /**
 7   * capacity:队列容量
 8   * fair:是否创建公平锁标识
 9   */ 
10 public ArrayBlockingQueue(int capacity, boolean fair) {
11     if (capacity <= 0)
12         throw new IllegalArgumentException();
13     // 队列数组
14     this.items = new Object[capacity];
15     // 创建锁
16     lock = new ReentrantLock(fair);
17     // 阻塞添加、获取元素的线程挂起、线程唤醒操作
18     notEmpty = lock.newCondition();
19     notFull =  lock.newCondition();
20 }

  通过ArrayBlockingQueue构造函数、成员变量可得出结论:ArrayBlockingQueue基于数组实现,内部分别维护存取元素的数组下标,阻塞存储元素通过ConditionObject实现。

1、添加元素

  ArrayBlockingQueue提供offer、add、put来添加元素,详情如下:

1.1、offer(E) - 添加元素

  ArrayBlockingQueue#offer(E)详情如下:

 1 public boolean offer(E e) {
 2     // 添加元素非空检验
 3     checkNotNull(e);
 4     // 获取成员变量锁对象
 5     final ReentrantLock lock = this.lock;
 6     // 加锁
 7     lock.lock();
 8     try {
 9         // 队列已满,返回false
10         if (count == items.length)
11             return false;
12         // 队列未满,元素添加进队列尾,返回true
13         else {
14             // 元素添加进队列尾
15             enqueue(e);
16             return true;
17         }
18     } finally {
19         // 释放锁
20         lock.unlock();
21     }
22 }
23 
24 // 添加元素至队列中
25 private void enqueue(E x) {
26     // 获取成员变量数组赋值给局部变量
27     final Object[] items = this.items;
28     // 将元素添加到数组指定下标的位置
29     items[putIndex] = x;
30     // 若添加的元素在数组的最大下标 (items.length - 1),将下一次添加元素对应的下标设置到数组 0 号位
31     // 构成了一个环形数组,重复利用
32     if (++putIndex == items.length)
33         putIndex = 0;
34     // 数组中元素个数 + 1
35     count++;
36     // 唤醒阻塞获取元素的线程
37     notEmpty.signal();
38 }

  offer在队列尾部添加元素,若队列中的数组元素已满,返回false;否则将元素添加进队列中,返回false。

1.2、offer(E,timeout,unit) - 添加元素,可能会阻塞指定时间

  ArrayBlockingQueue#offer(E,timeout,unit)详情如下:

 1 public boolean offer(E e, long timeout, TimeUnit unit)
 2     throws InterruptedException {
 3     // 添加元素非空校验
 4     checkNotNull(e);
 5     // 阻塞时间转化为纳秒
 6     long nanos = unit.toNanos(timeout);
 7     // 获取锁对象
 8     final ReentrantLock lock = this.lock;
 9     // 加锁,可通过Thread.interrupt()方法中断
10     lock.lockInterruptibly();
11     try {
12         // 队列已满
13         while (count == items.length) {
14             // 阻塞时间已达到,仍未添加成功,返回false
15             if (nanos <= 0)
16                 return false;
17             // 挂起当前线程并返回剩余阻塞时间;同时释放持有锁资源,当恢复执行时,需要重新获取锁资源
18             nanos = notFull.awaitNanos(nanos);
19         }
20         // 添加元素, 同offer(E)
21         enqueue(e);
22         // 返回添加成功标识
23         return true;
24     } finally {
25         // 释放锁
26         lock.unlock();
27     }
28 }

  offer(E,timeout,unit)添加数据到队列,如果队列满了,阻塞timeout时间,若阻塞时间已达到,仍未添加成功,返回false。

1.3、add(E) - 添加元素

  ArrayBlockingQueue是复用父类AbstractQueue的add方法,ArrayBlockingQueue#add(E) 核心代码如下:
public boolean add(E e) {
    return super.add(e);
}

  AbstractQueue#add(E)详情如下:

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

  add方法是基于offer方法实现的,若队列已满,与offer添加元素失败返回false不同,add添加元素失败会抛出IllegalStateException异常。

1.4、put(E) - 添加元素,可能阻塞

  AbstractQueue#add(E)详情如下:

 1 public void put(E e) throws InterruptedException {
 2     // 添加元素非空检验
 3     checkNotNull(e);
 4     // 获取锁对象
 5     final ReentrantLock lock = this.lock;
 6     // 加锁
 7     lock.lockInterruptibly();
 8     try {
 9         // 若队列已满,当前put元素线程挂起,同时释放持有所资源
10         while (count == items.length)
11             notFull.await();
12         // 队列未满,添加元素到队列尾,同offer(E)
13         enqueue(e);
14     } finally {
15         // 释放锁
16         lock.unlock();
17     }
18 }

  put方法是添加元素,若队列满了,会阻塞等待直到队列中有可添加的空间。

1.5、小结

添加元素方法的比较:

  ArrayBlocking提供不同的添加元素的方法,不同点在于当队列满了,处理及返回结果不同,详情如下:

方法  
不同点
offer(E)
队列容量已满,添加失败,直接返回false
offer(E,timeout,unit)
队列容量已满,阻塞等待timeout时间,若阻塞时间到达,仍添加失败,直接返回false
add(E)
基于offer实现,队列容量已满,抛出异常
put(E)
队列容量已满,阻塞等待直到队列中有可添加的空间

2、获取元素

  ArrayBlockingQueue提供peek、poll、take来获取元素,详情如下:

2.1、peek()

  ArrayBlockingQueue#peek() 核心代码如下:

 1 public E peek() {
 2     // 获取锁对象
 3     final ReentrantLock lock = this.lock;
 4     // 加锁
 5     lock.lock();
 6     try {
 7         // 获取队列元素,若队列为空返回null
 8         return itemAt(takeIndex); 
 9     } finally {
10         // 释放锁
11         lock.unlock();
12     }
13 }
14 
15 // 获取数组中指定下标的元素
16 final E itemAt(int i) {
17     return (E) items[i];
18 }

  peek()方法通过获取指定数组的下标的值得到队列头元素,在获取过程中不会对获取数组下标takeIndex做修改,也就是说peek方法仅会获取,不会删除队列中元素。若队列中元素为空,返回null。

2.2、poll()

  ArrayBlockingQueue#poll() 核心代码如下:

 1 public E poll() {
 2     // 获取锁对象
 3     final ReentrantLock lock = this.lock;
 4     // 加锁
 5     lock.lock();
 6     try {
 7         // 队列可获取元素为空,返回null;否则,获取队列中的头元素
 8         return (count == 0) ? null : dequeue();
 9     } finally {
10         // 释放锁
11         lock.unlock();
12     }
13 }
14 
15 // 获取队列中的元素
16 private E dequeue() {
17     // 将队列的数组引用赋值给局部变量
18     final Object[] items = this.items;
19     // 获取指定数组的下标的元素值 
20     E x = (E) items[takeIndex];
21     // 移除该下标的元素,即将指定数组下标的元素值设为null
22     items[takeIndex] = null;
23     // 若获取的元素在数组的最大下标 (items.length - 1),将下一次添加元素对应的下标设置到数组 0 号位
24     // 构成了一个环形数组,重复利用
25     if (++takeIndex == items.length)
26         takeIndex = 0;
27     // 队列中的元素数量减 1
28     count--;
29     if (itrs != null)
30         itrs.elementDequeued();
31     // 唤醒阻塞添加元素的线程
32     notFull.signal();
33     // 返回队列中指定下标的元素
34     return x;
35 }

  poll()方法有获取并移除元素的功能,若队列中元素为空,返回null。

2.3、poll(timeout, unit)

  ArrayBlockingQueue#poll(timeout, unit) 核心代码如下:

 1 public E poll(long timeout, TimeUnit unit) throws InterruptedException {
 2     // 将时间转化为纳秒
 3     long nanos = unit.toNanos(timeout);
 4     // 获取锁对象
 5     final ReentrantLock lock = this.lock;
 6     // 加锁
 7     lock.lockInterruptibly();
 8     try {
 9         // 队列元素为空
10         while (count == 0) {
11             // 阻塞时间已达到
12             if (nanos <= 0)
13                 // 返回null
14                 return null;
15             // 挂起当前线程并返回剩余阻塞时间;同时释放持有锁资源,当恢复执行时,需要重新获取锁资源
16             nanos = notEmpty.awaitNanos(nanos);
17         }
18         // 获取队列元素(同poll)
19         return dequeue();
20     } finally {
21         // 释放锁
22         lock.unlock();
23     }
24 }

  poll(timeout, unit) 与 offer(timeout, unit)类似,当队列中可取元素为空时,阻塞等待timeout时间,若阻塞时间已到达,返回null;若在阻塞时间内有可取元素,返回可取元素。

2.4、take()

  ArrayBlockingQueue#take() 核心代码如下:

 1 public E take() throws InterruptedException {
 2     // 获取锁对象
 3     final ReentrantLock lock = this.lock;
 4     // 加锁
 5     lock.lockInterruptibly();
 6     try {
 7         // 队列中无可取元素,即队列为空,阻塞等待,同时释放当前获取元素线程的锁资源
 8         while (count == 0)
 9             notEmpty.await();
10         // 队列中有可取元素,取元素 (同poll)
11         return dequeue();
12     } finally {
13         // 释放锁
14         lock.unlock();
15     }
16 }

  take()方法有阻塞获取并移除元素的功能,若队列中元素为空,阻塞等待,直到队列中有可取的元素。

2.5、小结

  获取元素方法的比较,ArrayBlocking提供不同的获取元素的方法,不同点在于当队列无可取元素,处理及返回结果不同,详情如下:
方法
不同点
peek()
队列无可取元素,获取失败,直接返回null
poll()
队列无可取元素,直接返回null
poll(timeout,unit)
队列无可取元素,阻塞等待timeout时间,若阻塞时间到达,仍获取失败,直接返回null
take()
队列无可取元素,阻塞等待直到队列中有可取元素

3、删除元素

 1 public boolean remove(Object o) {
 2     // 删除的元素为null,返回false
 3     if (o == null) return false;
 4     // 获取队列中的所有元素
 5     final Object[] items = this.items;
 6     // 获取锁对象
 7     final ReentrantLock lock = this.lock;
 8     // 加锁
 9     lock.lock();
10     try {
11         // 队列中的元素不为0
12         if (count > 0) {
13             // 获取下一次要添加元素对应的数组下标
14             final int putIndex = this.putIndex;
15             // 获取下一次要获取元素对应的数组下标
16             int i = takeIndex;
17             // equals 循环比较数组中的元素
18             do {
19                 // 要删除的元素是否与数组对应下标的元素相同
20                 if (o.equals(items[i])) {
21                     // 删除元素
22                     removeAt(i);
23                     // 返回删除成功标识
24                     return true;
25                 }
26                 // i 已到达数组的最大下表,将i设置到数组头部
27                 if (++i == items.length)
28                     i = 0;
29             // 下标不能超过下一次要添加元素对应的数组下标
30             } while (i != putIndex);
31         }
32         // 队列中元素为空 或 要删除的元素在队列中不存在,返回false
33         return false;
34     } finally {
35         // 释放锁
36         lock.unlock();
37     }
38 }

4、总结

  ArrayBlockingQueue基于数组实现元素存取,数组的首尾相连,构成环形数组。内部维护count成员变量,记录当前数组中的元素数量;维护putIndex变量,记录下一次要添加元素的数组下标;维护takeIndex变量,记录下一次获取元素的数组下标。

  ArrayBlockingQueue基于数组实现,是一个有界队列。基于ReentrantLock实现阻塞。

  ArrayBlockingQueue数据结构详情如下:

  

  添加元素时,优先判断count值是否等于数组长度,即队列是否满了,若队列满了,调用不同的添加方法有不同的结果;获取元素时,优先判断count值是否等于0,即队列是否为空,对队列为空,直接返回null。

  ArrayBlockingQueue阻塞添加、获取队列元素是基于AQS中的ConditionObject实现的。

 

posted @ 2023-04-27 23:11  无虑的小猪  阅读(33)  评论(0编辑  收藏  举报