一、前言:
1、什么是隊(duì)列?
隊(duì)列(Queue):是集合(Collection)的一個(gè)子類。隊(duì)列允許在一端進(jìn)行插入操作,而在另一端進(jìn)行刪除操作的線性表,而隊(duì)列的特點(diǎn)
是先進(jìn)先出。隊(duì)列的用處很大,比如實(shí)現(xiàn)消息隊(duì)列。
注意:棧的特點(diǎn)是后進(jìn)先出。

從上圖可以看出 Queue 大體可分為以下三類。
- 雙端隊(duì)列:雙端隊(duì)列(Deque)是 Queue 的子類也是 Queue 的補(bǔ)充類,頭部和尾部都支持元素插入和獲取。
- 阻塞隊(duì)列:阻塞隊(duì)列指的是在元素操作時(shí)(添加或刪除),如果沒有成功,會阻塞等待執(zhí)行。例如,當(dāng)添加元素時(shí),如果隊(duì)列元素已滿,隊(duì)列會阻塞等待直到有空位時(shí)再插入。
- 非阻塞隊(duì)列:非阻塞隊(duì)列和阻塞隊(duì)列相反,會直接返回操作的結(jié)果,而非阻塞等待。雙端隊(duì)列也屬于非阻塞隊(duì)列。
2、隊(duì)列的分類

| 隊(duì)列名稱 | 底層數(shù)據(jù)結(jié)構(gòu) | 是否有界 | 備注 |
|---|---|---|---|
| ArrayBlockingQueue | 數(shù)組 | 有界的 | 一個(gè)基于數(shù)組結(jié)構(gòu)的有界阻塞隊(duì)列,按照先進(jìn)先出的原則對元素進(jìn)行排序(初始化時(shí)指定隊(duì)列的大?。?。 |
| LinkedBlockingQueue | 鏈表 | 有界的 | 一個(gè)基于鏈表結(jié)構(gòu)的有界阻塞隊(duì)列,按照先進(jìn)先出的原則對元素進(jìn)行排序(默認(rèn)隊(duì)列大小Integer.MAX_VALUE)。 |
| PriorityBlockingQueue | 數(shù)組 | 無界的 | 一個(gè)支持優(yōu)先級排序的無界阻塞隊(duì)列(可以自定義比較器)。 |
| DelayQueue | 數(shù)組 | 無界的 | 一個(gè)支持延時(shí)獲取元素的無界阻塞隊(duì)列。 |
| SynchronousQueue | 鏈表 | 不存儲元素 | 一個(gè)不存儲元素的阻塞隊(duì)列,每個(gè)插入操作必須等待另一個(gè)線程的移除操作,否則插入操作一直處于阻塞狀態(tài)(同步的,一個(gè)offer必須與一個(gè)take與之相對應(yīng),否則放不了元素)。 |
| LinkedTransferQueue | 鏈表 | 無界的 | 一個(gè)基于鏈表結(jié)構(gòu)的無界阻塞隊(duì)列,支持生產(chǎn)者消費(fèi)者模式。 |
| ConcurrentLinkedQueue | 鏈表 | 無界的 | 一個(gè)基于鏈表結(jié)構(gòu)的無界并發(fā)隊(duì)列,按照先進(jìn)先出的原則對元素進(jìn)行排序(雙端數(shù)組隊(duì)列)。 |
3、什么時(shí)候使用隊(duì)列?
隊(duì)列是一種先進(jìn)先出(FIFO)的數(shù)據(jù)結(jié)構(gòu),通常適用于以下場景:
- 1、
處理大量的任務(wù)或請求:如果系統(tǒng)需要處理大量的任務(wù)或請求,并且需要保證這些任務(wù)或請求按照先來后到的順序依次處理,那么隊(duì)列數(shù)據(jù)結(jié)構(gòu)就可以發(fā)揮作用。比如,處理大量的網(wǎng)絡(luò)請求任務(wù)或消息處理。 - 2、
線程同步和通信:在多線程環(huán)境中,隊(duì)列可以用來進(jìn)行線程同步和通信。比如,線程池中的任務(wù)隊(duì)列,生產(chǎn)者-消費(fèi)者模型中的消息隊(duì)列等,可以把多個(gè)線程間的通信和同步問題簡單解決。 - 3、
消息中間件:隊(duì)列還常用于基于消息隊(duì)列的消息傳遞機(jī)制中,比如 ActiveMQ、RabbitMQ 等消息中間件,這些中間件的底層都實(shí)現(xiàn)了消息隊(duì)列的邏輯,允許生產(chǎn)者將消息放入隊(duì)列中,消費(fèi)者從隊(duì)列中拿到對應(yīng)的消息來處理。 - 4、
緩存:隊(duì)列還可以充當(dāng)緩存的角色,當(dāng)緩存滿時(shí),再來新的數(shù)據(jù)就可以利用隊(duì)列的原理,把最早的數(shù)據(jù)移出隊(duì)列,從而達(dá)到緩存數(shù)據(jù)的目的??傊?,隊(duì)列可以在許多領(lǐng)域和場景中找到應(yīng)用,特別是在處理和傳遞數(shù)據(jù)時(shí),可以通過隊(duì)列的先進(jìn)先出特性,簡單有效地實(shí)現(xiàn)多個(gè)任務(wù)、數(shù)據(jù)的異步處理和通信。
二、隊(duì)列的使用:
1、Queue 方法說明
Queue 常用方法,如下圖所示:

方法說明:
-
add(E):添加元素到隊(duì)列尾部,成功返回 true,隊(duì)列超出時(shí)拋出異常; -
offer(E):添加元素到隊(duì)列尾部,成功返回 true,隊(duì)列超出時(shí)返回 false; -
remove():刪除元素,成功返回 true,失敗返回 false; -
poll():獲取并移除此隊(duì)列的第一個(gè)元素,若隊(duì)列為空,則返回 null; -
peek():獲取但不移除此隊(duì)列的第一個(gè)元素,若隊(duì)列為空,則返回 null; -
element():獲取但不移除此隊(duì)列的第一個(gè)元素,若隊(duì)列為空,則拋異常。
2、Queue 使用實(shí)例
Queue<String> linkedList = new LinkedList<>();
linkedList.add("Dog");
linkedList.add("Camel");
linkedList.add("Cat");
while (!linkedList.isEmpty()) {
System.out.println(linkedList.poll());
}
程序執(zhí)行結(jié)果
Dog
Camel
Cat
3、阻塞隊(duì)列
1、BlockingQueue
BlockingQueue 在 java.util.concurrent 包下,其他阻塞類都實(shí)現(xiàn)自 BlockingQueue 接口,
BlockingQueue 提供了線程安全的隊(duì)列訪問方式,當(dāng)向隊(duì)列中插入數(shù)據(jù)時(shí),如果隊(duì)列已滿,線程則會阻塞等待隊(duì)列中元素被取出后再插入;當(dāng)從隊(duì)列中取數(shù)據(jù)時(shí),如果隊(duì)列為空,則線程會阻塞等待隊(duì)列中有新元素再獲取。
插入方法:
- add(E):添加元素到隊(duì)列尾部,成功返回 true,隊(duì)列超出時(shí)拋出異常;
- offer(E):添加元素到隊(duì)列尾部,成功返回 true,隊(duì)列超出時(shí)返回 false ;
- put(E):將元素插入到隊(duì)列的尾部,如果該隊(duì)列已滿,則一直阻塞。
刪除方法:
- remove(Object):移除指定元素,成功返回 true,失敗返回 false;
- poll(): 獲取并移除隊(duì)列的第一個(gè)元素,如果隊(duì)列為空,則返回 null;
- take():獲取并移除隊(duì)列第一個(gè)元素,如果沒有元素則一直阻塞。 檢查方法:
- peek():獲取但不移除隊(duì)列的第一個(gè)元素,若隊(duì)列為空,則返回 null。
2、LinkedBlockingQueue
LinkedBlockingQueue 是一個(gè)由鏈表實(shí)現(xiàn)的有界阻塞隊(duì)列,
容量默認(rèn)值為 Integer.MAX_VALUE,也可以自定義容量,建議指定容量大小,默認(rèn)大小在添加速度大于刪除速度情況下有造成內(nèi)存溢出的風(fēng)險(xiǎn),LinkedBlockingQueue 是先進(jìn)先出的方式存儲元素。
3、ArrayBlockingQueue
ArrayBlockingQueue
是一個(gè)有邊界的阻塞隊(duì)列,它的內(nèi)部實(shí)現(xiàn)是一個(gè)數(shù)組。它的容量是有限的,我們必須在其初始化的時(shí)候指定它的容量大小,容量大小一旦指定就不可改變。
ArrayBlockingQueue 也是先進(jìn)先出的方式存儲數(shù)據(jù),ArrayBlockingQueue 內(nèi)部的阻塞隊(duì)列是通過重入鎖 ReenterLock 和 Condition 條件隊(duì)列實(shí)現(xiàn)的,因此 ArrayBlockingQueue 中的元素存在公平訪問與非公平訪問的區(qū)別,對于公平訪問隊(duì)列,被阻塞的線程可以按照阻塞的先后順序訪問隊(duì)列,即先阻塞的線程先訪問隊(duì)列。而非公平隊(duì)列,當(dāng)隊(duì)列可用時(shí),阻塞的線程將進(jìn)入爭奪訪問資源的競爭中,也就是說誰先搶到誰就執(zhí)行,沒有固定的先后順序。
示例代碼如下:
// 默認(rèn)非公平阻塞隊(duì)列
ArrayBlockingQueue queue = new ArrayBlockingQueue(6);
// 公平阻塞隊(duì)列
ArrayBlockingQueue queue2 = new ArrayBlockingQueue(6,true);
// ArrayBlockingQueue 源碼展示
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();
}
4、DelayQueue
DelayQueue 是一個(gè)支持延時(shí)獲取元素的無界阻塞隊(duì)列,隊(duì)列中的元素必須實(shí)現(xiàn) Delayed 接口,
在創(chuàng)建元素時(shí)可以指定延遲時(shí)間,只有到達(dá)了延遲的時(shí)間之后,才能獲取到該元素。
實(shí)現(xiàn)了 Delayed 接口必須重寫兩個(gè)方法 ,getDelay(TimeUnit) 和 compareTo(Delayed),如下代碼所示:
class DelayElement implements Delayed {
@Override
// 獲取剩余時(shí)間
public long getDelay(TimeUnit unit) {
// do something
}
@Override
// 隊(duì)列里元素的排序依據(jù)
public int compareTo(Delayed o) {
// do something
}
}
DelayQueue 使用的完整示例 ,請參考以下代碼:
public class DelayTest {
public static void main(String[] args) throws InterruptedException {
DelayQueue delayQueue = new DelayQueue();
delayQueue.put(new DelayElement(1000));
delayQueue.put(new DelayElement(3000));
delayQueue.put(new DelayElement(5000));
System.out.println("開始時(shí)間:" + DateFormat.getDateTimeInstance().format(new Date()));
while (!delayQueue.isEmpty()){
System.out.println(delayQueue.take());
}
System.out.println("結(jié)束時(shí)間:" + DateFormat.getDateTimeInstance().format(new Date()));
}
static class DelayElement implements Delayed {
// 延遲截止時(shí)間(單面:毫秒)
long delayTime = System.currentTimeMillis();
public DelayElement(long delayTime) {
this.delayTime = (this.delayTime + delayTime);
}
@Override
// 獲取剩余時(shí)間
public long getDelay(TimeUnit unit) {
return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
// 隊(duì)列里元素的排序依據(jù)
public int compareTo(Delayed o) {
if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {
return 1;
} else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {
return -1;
} else {
return 0;
}
}
@Override
public String toString() {
return DateFormat.getDateTimeInstance().format(new Date(delayTime));
}
}
}
程序執(zhí)行結(jié)果:
開始時(shí)間:2019-6-13 20:40:38
2019-6-13 20:40:39
2019-6-13 20:40:41
2019-6-13 20:40:43
結(jié)束時(shí)間:2019-6-13 20:40:43
4、非阻塞隊(duì)列
1、ConcurrentLinkedQueue
ConcurrentLinkedQueue 是一個(gè)基于鏈接節(jié)點(diǎn)的無界線程安全隊(duì)列,
它采用先進(jìn)先出的規(guī)則對節(jié)點(diǎn)進(jìn)行排序,當(dāng)我們添加一個(gè)元素的時(shí)候,它會添加到隊(duì)列的尾部;當(dāng)我們獲取一個(gè)元素時(shí),它會返回隊(duì)列頭部的元素。
它的入隊(duì)和出隊(duì)操作均利用 CAS(Compare And Set)更新,這樣允許多個(gè)線程并發(fā)執(zhí)行,并且不會因?yàn)榧渔i而阻塞線程,使得并發(fā)性能更好。
ConcurrentLinkedQueue 使用示例:
ConcurrentLinkedQueue concurrentLinkedQueue = new ConcurrentLinkedQueue();
concurrentLinkedQueue.add("Dog");
concurrentLinkedQueue.add("Cat");
while (!concurrentLinkedQueue.isEmpty()) {
System.out.println(concurrentLinkedQueue.poll());
}
執(zhí)行結(jié)果:
Dog
Cat
可以看出不管是阻塞隊(duì)列還是非阻塞隊(duì)列,使用方法都是類似的,區(qū)別是底層的實(shí)現(xiàn)方式。
5、優(yōu)先級隊(duì)列
1、PriorityQueue
PriorityQueue 一個(gè)基于優(yōu)先級堆的無界優(yōu)先級隊(duì)列。
優(yōu)先級隊(duì)列的元素按照其自然順序進(jìn)行排序,或者根據(jù)構(gòu)造隊(duì)列時(shí)提供的 Comparator 進(jìn)行排序,具體取決于所使用的構(gòu)造方法。優(yōu)先級隊(duì)列不允許使用 null 元素。
PriorityQueue 代碼使用示例 :
Queue<Integer> priorityQueue = new PriorityQueue(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// 非自然排序,數(shù)字倒序
return o2 - o1;
}
});
priorityQueue.add(3);
priorityQueue.add(1);
priorityQueue.add(2);
while (!priorityQueue.isEmpty()) {
Integer i = priorityQueue.poll();
System.out.println(i);
}
程序執(zhí)行的結(jié)果是
3
2
1
PriorityQueue 注意的點(diǎn) :
PriorityQueue 是非線程安全的,在多線程情況下可使用 PriorityBlockingQueue 類替代;PriorityQueue 不允許插入 null 元素。
三、相關(guān)面試題:
1、ArrayBlockingQueue 和 LinkedBlockingQueue 的區(qū)別是什么?
答:ArrayBlockingQueue 和 LinkedBlockingQueue 都實(shí)現(xiàn)自阻塞隊(duì)列 BlockingQueue,它們的區(qū)別主要體現(xiàn)在以下幾個(gè)方面:
- ArrayBlockingQueue 使用時(shí)必須指定容量值,LinkedBlockingQueue 可以不用指定;
- ArrayBlockingQueue 的最大容量值是使用時(shí)指定的,并且指定之后就不允許修改;而 LinkedBlockingQueue 最大的容量為 Integer.MAX_VALUE;
- ArrayBlockingQueue 數(shù)據(jù)存儲容器是采用數(shù)組存儲的;而 LinkedBlockingQueue 采用的是 Node 節(jié)點(diǎn)存儲的。
2、LinkedList 中 add() 和 offer() 有什么關(guān)系?
答:add() 和 offer() 都是添加元素到隊(duì)列尾部。offer 方法是基于 add 方法實(shí)現(xiàn)的,Offer 的源碼如下:
public boolean offer(E e) {
return add(e);
}
3、Queue 和 Deque 有什么區(qū)別?
答:Queue 屬于一般隊(duì)列,Deque 屬于雙端隊(duì)列。一般隊(duì)列是先進(jìn)先出,也就是只有先進(jìn)的才能先出;而雙端隊(duì)列則是兩端都能插入和刪除元素。
4、LinkedList 屬于一般隊(duì)列還是雙端隊(duì)列?
答:LinkedList 實(shí)現(xiàn)了 Deque 屬于雙端隊(duì)列,因此擁有 addFirst(E)、addLast(E)、getFirst()、getLast() 等方法。
5、以下說法錯(cuò)誤的是?
A:DelayQueue 內(nèi)部是基于 PriorityQueue 實(shí)現(xiàn)的
B:PriorityBlockingQueue 不是先進(jìn)先出的數(shù)據(jù)存儲方式
C:LinkedBlockingQueue 默認(rèn)容量是無限大的
D:ArrayBlockingQueue 內(nèi)部的存儲單元是數(shù)組,初始化時(shí)必須指定隊(duì)列容量
答:C
題目解析:LinkedBlockingQueue 默認(rèn)容量是 Integer.MAX_VALUE,并不是無限大的。
6、.關(guān)于 ArrayBlockingQueue 說法不正確的是?
A:ArrayBlockingQueue 是線程安全的
B:ArrayBlockingQueue 元素允許為 null
C:ArrayBlockingQueue 主要應(yīng)用場景是“生產(chǎn)者-消費(fèi)者”模型
D:ArrayBlockingQueue 必須顯示地設(shè)置容量
答:B
題目解析:ArrayBlockingQueue 不允許元素為 null,如果添加一個(gè) null 元素,會拋 NullPointerException 異常。
7、以下程序執(zhí)行的結(jié)果是什么?
PriorityQueue priorityQueue = new PriorityQueue();
priorityQueue.add(null);
System.out.println(priorityQueue.size());
答:程序執(zhí)行報(bào)錯(cuò),PriorityQueue 不能插入 null。
8、Java 中常見的阻塞隊(duì)列有哪些?
答:Java 中常見的阻塞隊(duì)列如下:
- ArrayBlockingQueue,由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列;
- PriorityBlockingQueue,支持優(yōu)先級排序的無界阻塞隊(duì)列;
- SynchronousQueue,是一個(gè)不存儲元素的阻塞隊(duì)列,會直接將任務(wù)交給消費(fèi)者,必須等隊(duì)列中的添加元素被消費(fèi)后才能繼續(xù)添加新的元素;
- LinkedBlockingQueue,由鏈表結(jié)構(gòu)組成的阻塞隊(duì)列;DelayQueue,支持延時(shí)獲取元素的無界阻塞隊(duì)列。
9、有界隊(duì)列和無界隊(duì)列有哪些區(qū)別?
答:有界隊(duì)列和無界隊(duì)列的區(qū)別如下。
- 有界隊(duì)列:有固定大小的隊(duì)列叫做有界隊(duì)列,比如:new ArrayBlockingQueue(6),6 就是隊(duì)列的大小。
- 無界隊(duì)列:指的是沒有設(shè)置固定大小的隊(duì)列,這些隊(duì)列的特點(diǎn)是可以直接入列,直到溢出。它們并不是真的無界,它們最大值通常為 Integer.MAX_VALUE,只是平常很少能用到這么大的容量(超過 Integer.MAX_VALUE),因此從使用者的體驗(yàn)上,就相當(dāng)于 “無界”。
10、如何手動實(shí)現(xiàn)一個(gè)延遲消息隊(duì)列?
答:說到延遲消息隊(duì)列,我們應(yīng)該可以第一時(shí)間想到要使用 DelayQueue 延遲隊(duì)列來解決這個(gè)問題。實(shí)現(xiàn)思路,消息隊(duì)列分為生產(chǎn)者和消費(fèi)者,生產(chǎn)者用于增加消息,消費(fèi)者用于獲取并消費(fèi)消息,我們只需要生產(chǎn)者把消息放入到 DelayQueue 隊(duì)列并設(shè)置延遲時(shí)間,消費(fèi)者循環(huán)使用 take() 阻塞獲取消息即可。完整的實(shí)現(xiàn)代碼如下:
public class CustomDelayQueue {
// 消息編號
static AtomicInteger MESSAGENO = new AtomicInteger(1);
public static void main(String[] args) throws InterruptedException {
DelayQueue<DelayedElement> delayQueue = new DelayQueue<>();
// 生產(chǎn)者1
producer(delayQueue, "生產(chǎn)者1");
// 生產(chǎn)者2
producer(delayQueue, "生產(chǎn)者2");
// 消費(fèi)者
consumer(delayQueue);
}
//生產(chǎn)者
private static void producer(DelayQueue<DelayedElement> delayQueue, String name) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
// 產(chǎn)生 1~5 秒的隨機(jī)數(shù)
long time = 1000L * (new Random().nextInt(5) + 1);
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 組合消息體
String message = String.format("%s,消息編號:%s 發(fā)送時(shí)間:%s 延遲:%s 秒",
name, MESSAGENO.getAndIncrement(), DateFormat.getDateTimeInstance().format(new Date()), time / 1000);
// 生產(chǎn)消息
delayQueue.put(new DelayedElement(message, time));
}
}
}).start();
}
//消費(fèi)者
private static void consumer(DelayQueue<DelayedElement> delayQueue) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
DelayedElement element = null;
try {
// 消費(fèi)消息
element = delayQueue.take();
System.out.println(element);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
// 延遲隊(duì)列對象
static class DelayedElement implements Delayed {
// 過期時(shí)間(單位:毫秒)
long time = System.currentTimeMillis();
// 消息體
String message;
// 參數(shù):delayTime 延遲時(shí)間(單位毫秒)
public DelayedElement(String message, long delayTime) {
this.time += delayTime;
this.message = message;
}
@Override
// 獲取過期時(shí)間
public long getDelay(TimeUnit unit) {
return unit.convert(time - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
// 隊(duì)列元素排序
public int compareTo(Delayed o) {
if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS))
return 1;
else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS))
return -1;
else
return 0;
}
@Override
public String toString() {
// 打印消息
return message + " |執(zhí)行時(shí)間:" + DateFormat.getDateTimeInstance().format(new Date());
}
}
}
以上程序支持多生產(chǎn)者,執(zhí)行的結(jié)果如下:
生產(chǎn)者1,消息編號:1 發(fā)送時(shí)間:2019-6-12 20:38:37 延遲:2 秒 |執(zhí)行時(shí)間:2019-6-12 20:38:39
生產(chǎn)者2,消息編號:2 發(fā)送時(shí)間:2019-6-12 20:38:37 延遲:2 秒 |執(zhí)行時(shí)間:2019-6-12 20:38:39
生產(chǎn)者1,消息編號:3 發(fā)送時(shí)間:2019-6-12 20:38:41 延遲:4 秒 |執(zhí)行時(shí)間:2019-6-12 20:38:45
生產(chǎn)者1,消息編號:5 發(fā)送時(shí)間:2019-6-12 20:38:43 延遲:2 秒 |執(zhí)行時(shí)間:2019-6-12 20:38:45
......
11、總結(jié):
- 隊(duì)列(Queue)按照是否阻塞可分為:阻塞隊(duì)列 BlockingQueue 和 非阻塞隊(duì)列。
- 其中,雙端隊(duì)列 Deque 也屬于非阻塞隊(duì)列,雙端隊(duì)列除了擁有隊(duì)列的先進(jìn)先出的方法之外,還擁有自己獨(dú)有的方法,如 addFirst()、addLast()、getFirst()、getLast() 等,支持首未插入和刪除元素。
隊(duì)列中比較常用的兩個(gè)隊(duì)列還有 PriorityQueue(優(yōu)先級隊(duì)列)和 DelayQueue(延遲隊(duì)列),可使用延遲隊(duì)列來實(shí)現(xiàn)延遲消息隊(duì)列,這也是面試中比較常考的問題之一。