BlockingQueue
先进先出,出队列即移除。可用来实现消费者模式
BlockingQueue的核心方法:
放入数据:
offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
offer(E o, long timeout, TimeUnit unit),可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
获取数据:
peek():返回首位对象但不删除,如果队列空 返回null.
poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,
队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
ArrayBlockingQueue
使用数组实现的FIFO阻塞队列
构造方法
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
//依赖重入锁实现阻塞,默认是非公平锁 可以设置。
lock = new ReentrantLock(fair);
//使用lock创建 condition.说明 存取数据阻塞使用的是同一个锁
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
put方法
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//数据满的时候等待取数据执行
while (count == items.length)
notFull.await();
insert(e);//插入数据
} finally {
lock.unlock();
}
}
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);// (++i == items.length) ? 0 : i; 循环写入
++count;
notEmpty.signal();// 写入成功后 唤醒 notEmpty.await()
}
take
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//数据为空等待放数据
while (count == 0)
notEmpty.await();
return extract();//出队数据
} finally {
lock.unlock();
}
}
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);//取出数据 ,类型转换
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();//唤醒 notFull.await()
return x;
}
小结:ArrayBlockingQueue 使用一把锁来实现阻塞和等待唤醒,说明 其take(),put()的执行时串行的。也说明ArrayBlockingQueue在多线程并发下 吞吐量不高。
LinkedBlockingQueue
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {//当队列满的时候阻塞直到节点被出队
notFull.await();
}
enqueue(node);//入队节点
c = count.getAndIncrement();
if (c + 1 < capacity)唤醒其他put阻塞的线程。这个处理 take会执行,这里执行是帮助唤醒。
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)//只有满足了 take await的条件,才能执行唤醒。
signalNotEmpty();
}
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {//当队列为空 take 阻塞 直到有结点入队。
notEmpty.await();
}
x = dequeue();//出队头结点
c = count.getAndDecrement();//先获取 后减少count值
if (c > 1)//出队前 至少有2个以上节点
notEmpty.signal();// 唤醒其他take阻塞的线程。这个处理 put会执行,这里执行是帮助唤醒。
} finally {
takeLock.unlock();
}
if (c == capacity)//只有满足了put await的条件,才能执行put唤醒
signalNotFull();
return x;
}
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;//获取头节点的下个节点
h.next = h; // help GC 头节点的next指向自己, 出队的常用写法
head = first;//头结点指向原头节点的下个节点
E x = first.item;//获取节点数据实例
注意 这里获取的是头节点的下个节点数据。这个队列初始化的时候是创建了空的head节点,出队head后,原来存储数据的节点变成了头节点
first.item = null;//清理节点数据
return x;//返回节点数据。
}
总结:
LinkedBlockingQueue是一个阻塞队列,内部由两个ReentrantLock来实现出入队列的线程安全,由各自的Condition对象的await和signal来实现等待和唤醒功能。它和ArrayBlockingQueue的不同点在于:
- 队列大小有所不同,ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。
- 数据存储容器不同,ArrayBlockingQueue采用的是数组作为数据存储容器,而LinkedBlockingQueue采用的则是以Node节点作为连接对象的链表。
- 由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据的时,对于GC可能存在较大影响。
- 两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
参考资料:
BlockingQueue核心方法:http://wsmajunfeng.iteye.com/blog/1629354
LinkedBlockingQueue和ArrayBlockingQueue 分析:https://www.jianshu.com/p/b888a1689822
LinkedBlockingQueue分析的很详细,还有和ArrayBlockingQueue的对比:https://blog.csdn.net/tonywu1992/article/details/83419448