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) - 添加元素
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、小结
方法
|
不同点
|
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实现的。