介绍
阻塞队列,线程池与AsyncTask都是Android开发中经常用到的技术,其实这三种技术中是有引用关系的,AsyncTask将Runnable存储在线程池中,线程池内存储Runnable的位置是阻塞队列,接下来我们从下到上了解一下这三者的相关知识。
阻塞队列
简介
阻塞队列常用于生产者-消费者场景。特征为数据满的情况下,生产者生产会被阻塞;数据空的情况下,消费者消费会被阻塞。
BlockingQueue
- offer(Object):如果可能的话将object加入阻塞队列,返回是否成功,不阻塞当前线程。
- offer(Object,long timeout,TimeUnit):可以设置等待时间,时间到未能入队则返回false。
- put(Object):入队,如果没有空间会阻塞直到有空间。
- poll(time):出队,设置超时时间,时间到未取到返回null。
- poll(timeout,TimeUnit):?
- take():出队,队列为空则阻塞直到有数据。
- drainTo():一次性获取所有可用的数据对象。
Java中的阻塞队列
- ArrayBlockingQueue:数组组成的有界阻塞队列
- LinkedBlockingQueue:链表组成的有界阻塞队列
- PriorityBlockingQueue:支持优先级的无界阻塞队列
- DelayQueue:使用优先级队列实现的无界阻塞队列
- SyncchronousQueue:不存储元素的阻塞队列
- LinkedTransferQueue:由链表组成的无界阻塞队列
- LinkedBlockingDeque:由链表组成的双向阻塞队列
下面介绍几个将要用到的阻塞队列
1.ArrayBlockingQueue
数组实现的有界阻塞队列,按照先进先出的原则对元素进行排序,默认不保证公平访问队列(不保证队列可用时按照阻塞顺序访问),也可以手动创建公平队列:
//第一个参数是队列容量,第二个参数为true则为创建公平队列
ArrayBlockingQueue fairQueue=new ArrayBlockingQueue(2000,true);
2.LinkedBlockingQueue
使用链表实现的有界阻塞队列,和ArrayBlockingQueue功能类似。它对与生产者与消费者采用的独立控制的锁来控制同步,使得数据可以并行的读取和写入。在构造函数里应该指定阻塞队列的大小,如果未指定,则大小为Integer.MAX_VALUE。
3.PriorityBlockingQueue
支持优先级的无界阻塞队列,默认采用自然顺序升序排列,可以使用compareTo()方法来控制数据的排序规则,但不能保证同级元素的顺序。
4.DelayQueue
支持延迟的无界阻塞队列,队列中的元素必须实现Delayed接口,创建元素时可以指定元素到期时间,只有到期后元素才能被取走。
5.SynchronousQueue
不存储元素的阻塞队列,每个插入操作必须等待一个其他线程的移除操作,反之相同。
ArrayBlockingQueue关键源码解析
为了理解的更深刻些需要看下源码,先看类中定义的成员变量都有啥:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/** The queued items
* 队列中的元素存储在一个Object数组内。
*/
final Object[] items;
/** items index for next take, poll, peek or remove
* 当前队尾下标,出队时使用
*/
int takeIndex;
/** items index for next put, offer, or add
* 当前队首下标,入队时使用
*/
int putIndex;
/** Number of elements in the queue
* 当前队列中元素的个数
*/
int count;
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
* 重入锁和等待条件
*/
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
}
put方法:
/**
* 这个方法先获取了锁对象,并进行可中断的锁操作;
* 检测当前阻塞队列是否已满,如果满了,使用notNull条件进行等待;
* 如果当前阻塞队列有位置了,则进行入队操作;
* 方法执行后解锁。
*/
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
/**
* 入队方法。
*/
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length) putIndex = 0;
count++;
//注意这行,在入队之后,会唤醒被notEmpty条件阻塞的出队线程。
notEmpty.signal();
}
take方法:
/**
* 首先获取锁对象,并且进行可中断的锁操作;
* 检测阻塞队列为空时,使用notEmpty条件进行阻塞;
* 队列不为空时则出队;
* 方法执行后解锁。
*/
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue()