(一)、生产者-消费者问题
队列通常被视作线程间操作数据的容器,生产者将“生产”出来的数据放置在数据容器中,消费者只需要在“数据容器”中获取数据即可。阻塞队列(BlockingQueue)被广泛使用在“生产者-消费者”问题中,其原因是BlockingQueue提供了可阻塞的插入和可阻塞的移除方法。当队列容器满了,插入线程会被阻塞,直到队列容器空出位置。当队列容器空了,移除线程会被阻塞,直到队列容器不为空时。
(二)、BlockingQueue接口
public interface BlockingQueue<E> extends Queue<E> {
boolean add(E e);
boolean offer(E e);
void put(E e) throws InterruptedException;
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
E take() throws InterruptedException;
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
int remainingCapacity();
boolean remove(Object o);
public boolean contains(Object o);
int drainTo(Collection<? super E> c);
int drainTo(Collection<? super E> c, int maxElements);
}
因为BlockQueue接口继承自Queue接口,所以也有Queue接口中的方法,总结如下:
Queue
2.1 Queue接口中的方法
2.1.1 插入元素
方法 | 功能 |
---|---|
add(E e) | 往队列里插入元素,插入元素时会抛出illegalStateException |
offer(E e) | 往队列里插入元素,插入成功返回true,否则返回false。当队列满时不抛出异常 |
2.1.2 删除元素
方法 | 功能 |
---|---|
remove(Object e) | 从队列中删除数据,成功则返回true,否则返回false |
poll | 删除数据,当队列为空时,返回null |
2.1.3 查看元素
方法 | 功能 |
---|---|
element | 获取队头元素,如果队列为空时则抛出NoSuchElementException异常 |
peek | 获取队头元素,如果为空,返回null,不会抛出异常 |
2.2 BlockingQueue接口中的方法
2.2.1 插入元素
方法 | 功能 |
---|---|
put() | 当阻塞队列容量已经满时,往阻塞队列插入数据的线程会被阻塞,直到阻塞队列有空位 |
offer(E e,long timeout,TimeUnit unit) | 当阻塞队列已经满时,同样会阻塞插入数据的线程,直到阻塞队列有空位。但是它可以限定超时时间,如果超过时间就直接返回 |
2.2.2 删除数据
方法 | 功能 |
---|---|
take() | 当阻塞队列为空时,获取队头数据的线程会被阻塞 |
poll(long timeout,TimeUnit unit) | 当阻塞队列为空时,获取数据的线程会被阻塞。如果超过给定的时间,会直接返回 |
(三)、BlockingQueue具体实现
3.1 ArrayBlockingQueue
ArrayBlockingQueue是由数组实现的有界阻塞队列,有FIFO的特点。ArrayBlockingQueue可以作为“有界数据的缓冲区”,生产者插入数据到阻塞队列中,消费者进行获取。ArrayBlockingQueue一旦创建,不能再被修改。
ArrayBlockingQueue默认情况下是不保证队列的公平性的,也就是说等待时间最久的消费者不一定是优先获取“产品”的。有可能存在等待时间最久的消费者一直无法获得资源,造成饥饿。但是如果保证公平性,会降低BlockQueue的吞吐量。可以通过如下代码来实现公平性:
BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10,true);
3.2 LinkedBlockingQueue
LinkedBlockingQueue和ArrayBlockingQueue差不多,只不过是使用链表实现的,同样具有FIFO的特点,相比起来拥有更好的吞吐量。为了防止LinkedBlockingQueue容量增加过快,通常会指定长度。如果不指定长度,通常会使用Integer.MAX_VALUE。
3.3 PriorityBlockingQueue
PriorityBlockingQueue是一个支持优先级排序的无界阻塞队列,满足FIFO的特性,如果不指定就采用默认顺序进行排序,也可以通过自定义类实现compareTo()方法来指定元素排序规则,或者初始化时通过构造器Comparator来指定排序规则。
3.4 SynchronousQueue
SynchronousQueu实际上是不存储实际元素的,只有当有线程在删除数据时,其他线程才能插入数据。同样地,只有当有线程在插入数据时,其他线程才能删除数据。
3.5 LinkedTransferQueue
LinkedTransferQueue是一个由链表数据结构构成的无界阻塞队列,由于该队列实现了TransferQueue接口,与其他阻塞队列相比主要有以下不同的方法:
transfer(E e)
如果当前有线程(消费者)正在调用take()方法或者可延时的poll()方法进行消费数据时,生产者线程可以调用transfer方法将数据传递给消费者线程。如果当前没有消费者线程的话,生产者线程就会将数据插入到队尾,直到有消费者能够进行消费才能退出;
tryTransfer(E e)
tryTransfer方法如果当前有消费者线程(调用take方法或者具有超时特性的poll方法)正在消费数据的话,该方法可以将数据立即传送给消费者线程,如果当前没有消费者线程消费数据的话,就立即返回false。因此,与transfer方法相比,transfer方法是必须等到有消费者线程消费数据时,生产者线程才能够返回。而tryTransfer方法能够立即返回结果退出。
tryTransfer(E e,long timeout,imeUnit unit)
与transfer基本功能一样,只是增加了超时特性,如果数据才规定的超时时间内没有消费者进行消费的话,就返回false。
3.6 LinkedBlockingDeque
LinkedBlockingDeque是基于链表构成的有界阻塞双端队列,如果创建时不指定大小,默认值为Integer.Max_Value。这个阻塞队列不同点在于,是一个双端队列。
下面是LinkedBlockingQueue和LinkedBlockingDeque的对比:
3.7 DelayDeque
DelayDeque是实现了Delay接口的无界阻塞队列,只有当数据对象达到延迟时间了才能插入到队列中进行存储。如果当前所有的数据都还没有达到创建时所指定的延时期,则队列没有队头,并且线程通过poll等方法获取数据元素则返回null。所谓数据延时期满时,则是通过Delayed接口的getDelay(TimeUnit.NANOSECONDS)来进行判定,如果该方法返回的是小于等于0则说明该数据元素的延时期已满。
参考文章:并发容器之BlockingQueue