阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
常见阻塞场景
- 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。
- 当队列中填满数据的情况下,生产端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。
支持以上两种阻塞场景的队列被称为阻塞队列
BlockingQueue 的核心方法
放入数据:
- offer(an Object)
表示如果可能的话,将an Object 加到BlockingQueue 里。即如果BlockingQueue可以容纳,则返回true,否则返回false。(本方法不阻塞当前执行方法的线程)
- offer(E o,long timeout,TimeUnit unit)
可以设定等待的时间。如果在指定的时间内还不能往队列中加入BlockQueue ,则返回失败。
- put(an Object)
将anObject 加到BlockingQueue 里。如果BlockingQueue 没有空间,则调用此方法的线程被阻断,直到BlockingQueue 里有空间再继续。
获取数据:
- poll(time)
取走BlockingQueue里排在首位的对象。若不能立即取出,则可以等time参数规定的时间,取不到时返回null。
- poll(long timeout,TimeUnit unit)
从BlockingQueue中取出一个队首的对象。如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据;否则直到时间超时还没有数据可取,返回失败。
- take()
取走BlockingQueue里排在首位的对象。若BlockingQueue 为空,则阻断进入等待状态,直到BlockingQueue有新的数据被加入。
- drainTo()
一次性从BlockingQueue 获取所有可用的数据对象(还可以指定获取数据的个数)。通过该方法,可以提升获取数据的效率;无须多次分批加锁或释放锁。
Java 中的阻塞队列
- ArrayBlockingQueue
用数组实现的有界阻塞队列,并按照先进先出的原则对元素进行排序。默认情况下不保证线程公平地访问队列。公平访问队列就是指阻塞的所有生产者线程或者消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列。即先阻塞的生产者线程,可以先往队列里插入元素;先阻塞的消费者线程,可以先从队列里获取元素。
- LinkedBlockingQueue
基于链表的阻塞队列,此队列按照先进先出(FIFO)的原则对元素进行排序,其内部也维持着一个数据缓冲队列(该队列由一个链表构成)。
当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到缓存容量的最大值时(LinkedBlockingQueue 可以通过构造方法指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒。反之,对于消费者这端的处理也基于同样的原理。
LinkedBlockingQueue之所以能够高效地处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步。这意味着在高并发的情况下生产者端和消费者端可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
注意: 如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE)。这样一来,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。
- PriorityBlockingQueue
它是一个支持优先级的无界队列。默认情况下,元素采取自然顺序排列。这里可以自定义实现compareTo()方法来指定元素进行排序规则;或者初始化PriorityBlockingQueue时,指定构造参数Comparator 来对元素进行排序,但不能保证同优先级元素的顺序。
- DelayQueue
它是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue 来实现。队列中的元素必须实现Delayed接口。创建元素时,可以指定元素到期的时间,只有在元素到期时才能从队列中取走。
- SynchronousQueue
它是一个不存储元素的阻塞队列。每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。因此此队列内部其实没有任何一个元素,或者说容量
是0.
- LinkedTransferQueue
它是一个由链表结构组成的无界阻塞TransferQueue 队列。LinkedTransferQueue实现了一个重要接口TransferQueue。
- LinkedBlockingDeque
它是一个由链表结构组成的双向阻塞队列。双向队列可以从队列的两端插入和移出元素,因此在多线程同时入队时,也就减少了一半的竞争。
使用场景
- ArrayBlockingQueue(阻塞队列) 实现的生产者-消费者模式
public class Test {
private int queueSize=10;
// 默认情况下 不保证线程公平地访问队列
// true :公平访问队列
private ArrayBlockingQueue<Integer> queue=new ArrayBlockingQueue<>(queueSize,true);
public static void main(String[] args) {
Test test=new Test();
Producer producer=test.new Producer();
producer.start();
Consumer consumer=test.new Consumer();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
while (true) {
try {
queue.take();
System.out.println("消费一个");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
while (true) {
try {
queue.put(1);
System.out.println("生产一个");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
运行结果:
生产一个
生产一个
消费一个
消费一个
生产一个
生产一个
消费一个
消费一个
- PriorityQueue(非阻塞队列) 实现的生产者-消费者模式
public class ProducerConsumerDemo {
private int queueSize=10;
private PriorityQueue<Integer> queue=new PriorityQueue<>(queueSize);
public static void main(String[] args) {
ProducerConsumerDemo demo=new ProducerConsumerDemo();
Producer producer=demo.new Producer();
producer.setName("生产者");
producer.start();
Consumer consumer=demo.new Consumer();
consumer.setName("消费者");
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
while (true) {
synchronized (queue) {
while(queue.size()==0) {
try {
System.out.println(Thread.currentThread().getName()+":队列空,等待数据");
queue.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
queue.notify();
}
}
// 每次移走数据
queue.poll();
System.out.println(Thread.currentThread().getName()+":消费1个");
queue.notify();
System.out.println(Thread.currentThread().getName()+":去唤醒生产者");
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
while (true) {
synchronized (queue) {
while(queue.size()==queueSize) {
try {
System.out.println(Thread.currentThread().getName()+":队列满,等待有空余的空间");
queue.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
queue.notify();
}
}
// 每次插入1个数据
queue.offer(1);
System.out.println(Thread.currentThread().getName()+":生产1个");
queue.notify();
System.out.println(Thread.currentThread().getName()+":去唤醒消费者");
}
}
}
}
}
运行结果:
生产者:生产1个
生产者:去唤醒消费者
生产者:生产1个
生产者:去唤醒消费者
生产者:生产1个
生产者:去唤醒消费者
生产者:生产1个
生产者:去唤醒消费者
生产者:队列满,等待有空余的空间
消费者:消费1个
消费者:去唤醒生产者
消费者:消费1个
消费者:去唤醒生产者
消费者:消费1个
消费者:去唤醒生产者
消费者:消费1个
消费者:去唤醒生产者
消费者:消费1个
消费者:去唤醒生产者
消费者:消费1个
消费者:去唤醒生产者
消费者:消费1个
消费者:去唤醒生产者
消费者:消费1个
消费者:去唤醒生产者
消费者:消费1个
消费者:去唤醒生产者
消费者:消费1个
消费者:去唤醒生产者
消费者:队列空,等待数据
生产者:生产1个
生产者:去唤醒消费者