1.基本思路
之前接触的队列都是非阻塞队列,不如LinkedList(实现了Dequeue接口)、PriorityQueue。使用非阻塞队列有一个很大的问题就是,它不会对当前线程产生阻塞。那么在面对类似生产者-消费者问题时,就必须额外实现同步策略以及线程间唤醒策略,这个实现起来非常麻烦。但是有了阻塞队列就不一样了,它会对当前线程产生阻塞。比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞,直到阻塞队列中有了元素。当队列中有了元素之后,被阻塞的线程会自动被唤醒(不需要自己写代码)。这样就提供了极大的方便。
2.阻塞队列类型
(1)ArrayBlockingQueue
必须指定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的线程能够最优先访问队列
(2)LinkedBlockingQueue
基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE
(3)PriorityBlockingQueue
上面两种都是先进先出队列,而PriorityBlockingQueue不是,他会按照元素的优先级对元素进行排序,按照优先级顺序出队列,每次出队列都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限。前面两种都是有界队列。
(4)DelayQueue
基于PriorityQueue,是一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到元素。DelayQueue也是一个无界队列,因此往此队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞
3.阻塞队列中方法vs非阻塞队列中方法
(1)非阻塞队列中方法
1)add(E e):元素添加到末尾,如果添加成功返回true;如果插入失败(队列已满),则抛出异常
2)remove():移除队首元素,如果移除成功返回true;如果移除失败(队列空),则抛出异常
3)offer(E e):元素添加到末尾,如果添加成功返回true;如果插入失败(队列已满),则返回false
4)poll():移除队首元素,如果移除成功返回true;如果移除失败(队列空),则false
5)peek():获取队首元素,如果成功返回队首元素;否则返回null
对于非阻塞队列,一般情况下建议使用offer、poll、peek方法,不建议使用add和remove方法。因为使用offer、poll、peek三个方法可以通过返回值判断操作成功与否。注意:非阻塞队列中的方法都没有同步措施
(2)阻塞队列中方法
除了上面的5个方法,还提供了另外4个非常有用的方法:
1)put(E e):向队尾插入元素,如果队列满,则等待
2)take():从队首取元素,如果队列为空,则等待
3)offer(E e,long timeout,TimeUnit unit):向队尾存入元素,如果队列满则等待timeout的时间,如果到达时间期限,还没有插入成功则返回false,否则返回true
4)poll(long timeout,TimeUnit unit):从队首取元素,如果队列为空则等待tmeout的时间,如果到达时间期限,还没有取到成功则返回null,否则返回队首元素
4.阻塞队列实现原理
以ArrayBlockingQueue为例,看下其中的成员变量
private final E[] items; //用来存储元素
private int takeIndex; //队首下标
private int putIndex; //队尾下标
private int count; //元素个数
private final ReentrantLock lock;
private final Condition notEmpty; //等待条件,不为空
private final Condition notFull;
构造方法:
public ArrayBlockingQueue(int capacity, boolean fair)
{
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = (E[]) new Object[capacity]; //申请一个数组
lock = new ReentrantLock(fair); //实际上是指定公平还是非公平锁
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
put方法:
public void put(E e) throws InterruptedException
{
if (e == null) throw new NullPointerException();
final E[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try
{
try
{
while (count == items.length)
notFull.await();
}
catch (InterruptedException ie)
{
notFull.signal(); // propagate to non-interrupted thread
throw ie;
}
insert(e);
}
finally
{
lock.unlock();
}
}
再看一下其中的insert方法:
private void insert(E x)
{
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
那么就可以看出,实际上就是一个生产者-消费者的模型。也就是先使用lock加锁,再判断队列是否满,满的话则等待,然后将数据插入队列,最后则唤醒消费者线程
take()方法
public E take() throws InterruptedException
{
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try
{
while (count == 0)
notEmpty.await();
return extract();
}
finally
{
lock.unlock();
}
}
其中的extract()方法:
private E extract()
{
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();
return x;
}
实际上就是一个消费者的操作