JDK 源码解析 —— ArrayBlockingQueue

零. 简介
ArrayBlockingQueue 是一个由 数组作为基础数据结构的 有界阻塞队列。出队入队使用先进先出算法,即 FIFO (first in first out)。head 是停留在队列中最长的节点,tail 停留在队列中最短的节点。从 head 出队列,从 tail 入队列。

这是一个典型的「有界缓冲区」,一个固定大小的数组持有从生产者(producers)产生的和被消费者(consumers)消费的数据。 此队列一旦创建,队列大小就固定了,不能再改变。尝试将数据加入到满队列中,将会被阻塞;尝试从空队列中取数据也同样会被阻塞。

本类提供生产者和消费者线程访问顺序的一个可选的公平策略。默认情况下,为了性能,这个访问顺序不被保证。不过,在初始化构造本类的时候,将 fairness 设置为 true,则可以保证线程访问的公平性,这样设置的坏处是降低吞吐量,好处是减少了可变性和线程的饥饿现象。


一. 代码

(1)核心变量

[java]  view plain  copy
  1. /** Main lock guarding all access */  
  2. final ReentrantLock lock;  
  3. /** Condition for waiting takes */  
  4. private final Condition notEmpty;  
  5. /** Condition for waiting puts */  
  6. private final Condition notFull;  
阻塞队列实现的关键参数,一个可重入锁,和两个条件,使用经典的双状态算法(two-condition algorithm)

[java]  view plain  copy
  1. /** The queued items */  
  2. final Object[] items;  
  3.   
  4. /** items index for next take, poll, peek or remove */  
  5. int takeIndex;  
  6.   
  7. /** items index for next put, offer, or add */  
  8. int putIndex;  
  9.   
  10. /** Number of elements in the queue */  
  11. int count;  
一些基本参数,基础的数组对象,队头的位置,队尾的位置,队列中已有的数据量


(2)构造器

[java]  view plain  copy
  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. }  

队列构造方法,设置固定队列大小,是否需要公平访问队列,公平锁和非公平锁由 ReentrantLock (公平锁是正常走锁排队申请流程,非公平锁先尝试获取 AQS stat 状态锁,然后才走正常锁排队申请)提供


[java]  view plain  copy
  1. public ArrayBlockingQueue(int capacity) {  
  2.     this(capacity, false);  
  3. }  
默认构造方法是非公平的



(3) add 方法:新增数据

实际上是调用继承的抽象类 AbstractQueue 的 add 方法

[java]  view plain  copy
  1. public boolean add(E e) {  
  2.     return super.add(e);  
  3. }  
  4. public boolean add(E e) {  
  5.     if (offer(e))  
  6.         return true;  
  7.     else  
  8.         throw new IllegalStateException("Queue full");  
  9. }  

上述的 offer(e) 是接口 Queue 未实现的方法,具体实现在 ArrayBlockingQueue

[java]  view plain  copy
  1. public boolean offer(E e) {  
  2.     checkNotNull(e);  
  3.     final ReentrantLock lock = this.lock;  
  4.     lock.lock();  
  5.     try {  
  6.         if (count == items.length)  
  7.             return false;  
  8.         else {  
  9.             insert(e);  
  10.             return true;  
  11.         }  
  12.     } finally {  
  13.         lock.unlock();  
  14.     }  
  15. }  
如果队列还没满,则加入队尾并返回 true; 可以看出来 offer 方法如果插入不了不是进入阻塞状态,是直接返回一个 false 状态


将数据插入队尾,移动数组下标( inc(putIndex) 保证循环移动),队列总数 count 加 1,notEmpty.signal 唤醒等待拿数据的线程(在 AQS 的等待队列中的线程)

[java]  view plain  copy
  1. private void insert(E x) {  
  2.     items[putIndex] = x;  
  3.     putIndex = inc(putIndex);  
  4.     ++count;  
  5.     notEmpty.signal();  
  6. }  



(4)put 方法:新增数据 (如果满了就阻塞)

[java]  view plain  copy
  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.         insert(e);  
  9.     } finally {  
  10.         lock.unlock();  
  11.     }  
  12. }  
可以看到如果队列数据量 count == items.length 数组大小,则线程阻塞 await()


(5)poll:取数据,不是阻塞方法

[java]  view plain  copy
  1. public E poll() {  
  2.     final ReentrantLock lock = this.lock;  
  3.     lock.lock();  
  4.     try {  
  5.         return (count == 0) ? null : extract();  
  6.     } finally {  
  7.         lock.unlock();  
  8.     }  
  9. }  
  10.   
  11. private E extract() {  
  12.     final Object[] items = this.items;  
  13.     E x = this.<E>cast(items[takeIndex]);  
  14.     items[takeIndex] = null;  
  15.     takeIndex = inc(takeIndex);  
  16.     --count;  
  17.     notFull.signal();  
  18.     return x;  
  19. }  
如果数据为空则返回 null,不为空则返回数据,并且唤醒 notFull 状态挂起的线程


(5)take:取数据,如果为空则阻塞

[java]  view plain  copy
  1. public E take() throws InterruptedException {  
  2.     final ReentrantLock lock = this.lock;  
  3.     lock.lockInterruptibly();  
  4.     try {  
  5.         while (count == 0)  
  6.             notEmpty.await();  
  7.         return extract();  
  8.     } finally {  
  9.         lock.unlock();  
  10.     }  
  11. }  


(6) drainTo 方法:字面意思就是排干,就是把数据批量导入到一个集合类中,比一个一个 poll 效率高,因为加锁次数少

[java]  view plain  copy
  1. public int drainTo(Collection<? super E> c) {  
  2.     checkNotNull(c);  
  3.     if (c == this)  
  4.         throw new IllegalArgumentException();  
  5.     final Object[] items = this.items;  
  6.     final ReentrantLock lock = this.lock;  
  7.     lock.lock();  
  8.     try {  
  9.         int i = takeIndex;  
  10.         int n = 0;  
  11.         int max = count;  
  12.         while (n < max) {  
  13.             c.add(this.<E>cast(items[i]));  
  14.             items[i] = null;  
  15.             i = inc(i);  
  16.             ++n;  
  17.         }  
  18.         if (n > 0) {  
  19.             count = 0;  
  20.             putIndex = 0;  
  21.             takeIndex = 0;  
  22.             notFull.signalAll();  
  23.         }  
  24.         return n;  
  25.     } finally {  
  26.         lock.unlock();  
  27.     }  
  28. }  



三. 总结
看懂 ArrayBlockingQueue 需要先看懂 AbstractQueuedSynchronizer 和 ReentrantLock,阻塞就是靠 ReentrantLock 来实现的,而 ReentrantLock 是靠 AbstractQueuedSynchronizer 来实现加锁和释放锁。主要的算法就是上文提到的 two-condition algorithm,这个算法应该在学生时代《操作系统》课程上见过很多次了 


转发自:https://blog.csdn.net/wenniuwuren/article/details/51283505

猜你喜欢

转载自blog.csdn.net/jr_way/article/details/80173813