队列是一种常见的数据结构,先进先出,在日常工作中有着高频率的使用。在 Java 中应用也更为广泛,是生产消费模型首选的数据结构,简化了开发,解耦生产与消费的关系。Java中 Queue 可以分为阻塞队列和非阻塞队列。
要说这两者的区别,首先要了解一下阻塞队列与非阻塞队列的区别
一、阻塞队列与非阻塞队列
1、非阻塞队列
非阻塞队列也就是一般的队列,没有阻塞队列的两个阻塞功能。其主要方法如下:
-
boolean add(E e):将元素e插入到队列末尾,插入成功,返回true;插入失败(即队列已满),抛出异常;
-
boolean offer(E e):将元素e插入到队列末尾,插入成功,则返回true;插入失败(即队列已满),返回false;
-
E remove():移除队首元素,若移除成功,则返回true;移除失败(队列为空),则会抛出异常;
-
E poll():获取队首元素并移除,若队列不为空,则返回队首元素;否则返回null;
-
E element():获取队首元素并不移除元素,若队列不为空,则返回队首元素;否则抛出异常;
-
E peek():获取队首元素并不移除元素,若队列不为空,则返回队首元素;否则返回null;
2、阻塞队列
队列一般都有着两个阻塞操作,即插入与取出。
当队列满时,会阻塞元素的插入,直到队列有空闲时停止阻塞,新元素才可以继续插入。
当队列为空时,移除元素的线程会一直被阻塞等待,直到队列中有元素时才可以继续取出。
除拥有普通队列的方法之外,阻塞队列提供了另外4个常用的方法:
put(E e):向队尾插入元素,若队列已满,则被阻塞等待,直到有空闲才继续插入。
take():从队首取出元素,若队列为空,则被阻塞等待,直到有元素才继续取出。
offer(E e,long timeout, TimeUnit unit):向队尾插入元素,若队列已满,则计时等待,当时间期限达到时,若队列还是满的,则返回false;若等待在期限内,队列空闲,则插入成功,返回true;
poll(long timeout, TimeUnit unit):从队首取出元素,如果队列空,则计时等待,当时间期限达到时,若队列还是空的,则返回null;若等待在期限内,队列中有元素,否则返回取得的元素;
二、LinkedBlockingQueue
由于 LinkedBlockingQueue 实现是线程安全的,实现了先进先出等特性,是作为生产者消费者的首选,LinkedBlockingQueue 可以指定容量,也可以不指定,不指定长度的话,默认是 Integer.MAX_VALUE,即无界队列。LinkedBlockingQueue比普通队列多出的方法参见上边阻塞队列方法。
三、ConcurrentLinkedQueue
ConcurrentLinkedQueue 也是 Queue 的一个安全实现,是非阻塞队列,其方法参见上边非阻塞队列。队列中元素按 FIFO 原则进行排序。采用 CAS操作,来保证元素的一致性。
四、二者区别
首先二者都是线程安全的得队列,都可以用于生产与消费模型的场景。
LinkedBlockingQueue是阻塞队列,其好处是:多线程操作共同的队列时不需要额外的同步,由于具有插入与移除的双重阻塞功能,对插入与移除进行阻塞,队列会自动平衡负载,从而减少生产与消费的处理速度差距。
由于LinkedBlockingQueue有阻塞功能,其阻塞是基于锁机制实现的,当有多个线程消费时候,队列为空时线程都被阻塞,若不设置超时机制,线程在不停的空转,耗费系统资源。从此方面来讲,LinkedBlockingQueue更适用于多线程插入,单线程取出,即多个生产者与单个消费者。
ConcurrentLinkedQueue非阻塞队列,采用 CAS操作,解决多线程之间的竞争,来保证元素的一致性,效率更高。当许多线程共享访问一个公共集合时,ConcurrentLinkedQueue 是一个恰当的选择。从此方面来讲,ConcurrentLinkedQueue更适用于单线程插入,多线程取出,即单个生产者与多个消费者。
总之,对于几个线程生产与几个线程消费,二者并没有严格的规定, 只有谁更适合。
参考:https://blog.csdn.net/A1023824314/article/details/52263932
参考:https://www.cnblogs.com/linjiqin/archive/2013/05/30/3108188.html