ArrayBlockingQueue是一个有界阻塞队列,内部用数组存储元素,初始化的需要指定容量大小,底层用ReentrantLock来保证线程安全.
例子如下:
public class ArrayBlockingQueueDemo {
public static void main(String[] args) throws Exception {
//使用ArrayBlockingQueue初始化一个BlockingQueue,指定容量的上限为1024
BlockingQueue queue = new ArrayBlockingQueue(1024);
//生产者
Producer producer = new Producer(queue);
//消费者
Consumer consumer = new Consumer(queue);
//开启生产者线程
new Thread(producer).start();
//开启消费者线程
new Thread(consumer).start();
Thread.sleep(4000);
}
}
生产者如下:
public class Producer implements Runnable {
protected BlockingQueue queue = null;
public Producer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
queue.put(i);
System.out.println("produce " + i);
Thread.sleep(10000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
消费者如下:
public class Consumer implements Runnable{
protected BlockingQueue queue = null;
public Consumer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
System.out.println("consumer "+queue.take());
System.out.println("consumer "+queue.take());
System.out.println("consumer "+queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果如下:
1:从运行结果可以看出来,生产者放入一个值,就会消费一个.生产者和消费者效率速度匹配的情况下还是不错的.如果任何一方过快或者过慢,都会造成线程的阻塞.
2:从这个例子中,我们可以看到几个关键的方法.
方法探究:
1:构造方法:
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();
}
1.1:从构造方法我们可以看出,这个阻塞队列是支持公平和非公平两种模式.
1.2:如果我们传入的容量参数小于等于0会抛出异常.
1.3:内部存储元素的数据结构是一个数组.
1.4:内部运用的锁是ReentrantLcok,阻塞队列运用的是condition.
(ReentrantLcok前面有相关的文章可以参考,这里就不过多赘述了.)
2:构造方法
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
这个构造方法和上面的区别是这个可以传入一个数组.
2.1:通过获取锁操作,来保证多线程下的初始化唯一.
2.2:通过循环给items数组进行赋值.
2.3:checkNotNull方法:
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
}
如果传入的数组为空的话抛出空指针异常.
2.4:count:当前队列里的元素数量.
2.5:putIndex:放入元素的指针.(就是位置,如果i等于当前容量,就重置为0,是一个环形数组.).
3.put方法:
void put(E e) throws InterruptedException;
3.1:enqueue方法:
注意:这里的唤醒并不是真正的唤醒,只是把线程从条件队里转移到同步队列,然后释放锁的时候去真正唤醒.
4.take方法:
E take() throws InterruptedException;
4.1dequeue方法:
4.2:itrs是一个迭代器,我们可以通过迭代器遍历这个阻塞队列,一般情况下这个都是空.
4.3elementDequeued方法:
void elementDequeued() {
// assert lock.getHoldCount() == 1;
if (count == 0)
queueIsEmpty();
else if (takeIndex == 0)
takeIndexWrapped();
}
4.4如果当前队列没有元素的话,进入queueIsEmpty方法:
4.5如果取指针为0的话,进入takeIndexWrapped方法:
void takeIndexWrapped() {
// assert lock.getHoldCount() == 1;
cycles++;
for (Node o = null, p = head; p != null;) {
final Itr it = p.get();
final Node next = p.next;
if (it == null || it.takeIndexWrapped()) {
// unlink p
// assert it == null || it.isDetached();
p.clear();
p.next = null;
if (o == null)
head = next;
else
o.next = next;
} else {
o = p;
}
p = next;
}
if (head == null) // no more iterators to track
itrs = null;
}
这个方法大概就是要把已经出队的元素进行清除.(这个阻塞队列的迭代器没有太多深入了解,有懂的小伙伴,可以指点一二,让我少走点弯路.)
没有伞的孩子,他,只能奔跑.