ArrayBlockingQueue概念
基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。队列的头部是在队列中存在时间最长的元素。对列的尾部是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列的头部获取元素的。
这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和消费者提取的元素。一段创建了这样的缓存区,就不能再增加其容量。试图向已满的队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似受阻塞。
此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略(FIFO)。默认情况下是非公平策略(LIFO)。
然而,通过将公平性(fairness)设置为true而构造的队列允许按照FIFO顺序访问线程。公平性会降低吞吐量。
ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue。
先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。从某种程度上来说这种队列也体现了一种公平性。
后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件。
ArrayBlockingQueue部分源码
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
//数组的存储结构
final Object[] items;
//锁采用的机制
final ReentrantLock lock;
//只限制长度的队列,起公平性设置了false
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
//创建队列
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
//生产者放入元素到队列中,若队列已满,返回false
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
//对列已满则受阻塞
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
//消费者获取元素,对列为空时受阻塞
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} 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.signal();
}
}
应用示例
需求:
* 现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,
* 请在程序中增加4个线程去调用parseLog()方法分头打印这16个日志对象,
* 程序只需运行4s即可完成打印这新日志对象
package test;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
final BlockingQueue<String> bq = new ArrayBlockingQueue<String>(16);
//创建四个线程去取,并调用打印方法,观察是否为先进先出:使用take方式读取,若队列为空则阻塞,直至队列有值
for(int i=0;i<4;i++){
new Thread(
new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
String log = bq.take();
parseLog(log);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
).start();
}
//将日志全部放在队列中。使用put--队列满了处于阻塞状态,直至队列可用
for(int i=0;i<16;i++){
String log = (i+1)+"------>";
try {
System.out.println("put :"+log);
bq.put(log);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void parseLog(String log){
System.out.println(log + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}