ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按FIFO(先進先出)原則對元素進行排序。
LinkedBlockingQueue:是一個基於鏈表結構的阻塞隊列,此隊列按FIFO(先進先出)排序元素,吞吐量通常高於ArrayBlockingQueue。
SynchronousQueue:一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於SynchronousQueue。
阻塞隊列的好處
在多線程領域:所謂阻塞,在某些情況下會掛起線程,一旦條件滿足,被掛起的線程又會被自動喚醒。
爲什麼需要BlockingQueue
好處是我們不需要關心什麼時候需要阻塞線程,什麼時候需要喚醒線程,因爲一切BlockingQueue都給你一手包辦了。
在Concurrent包發佈以前,在多線程環境下,我們每個程序員都必須去自己控制這些細節,尤其還要兼顧效率和線程安全,這會給我們的程序帶來不小的複雜度。
阻塞隊列的核心方法
方法類型 |
拋出異常 |
特殊值 |
阻塞 |
超時 |
插入 |
add(e) |
offer(e) |
put(e) |
offer(e,time,unit) |
移除 |
remove() |
poll() |
take() |
poll(time,uni) |
檢查 |
element() |
peek() |
不可用 |
不可用 |
拋出異常 |
當阻塞隊列滿時,再往隊列裏add插入元素會拋出IllegalStateException: Queue full 當阻塞隊列空時,再往隊列裏remove移除元素會拋出NoSuchElementException 當阻塞隊列爲空時,使用element判斷第一個元素時拋出NoSuchElementException |
特殊值 |
offer(),插入方法,成功true失敗false poll(),移除方法,成功返回出隊列的元素,隊列中沒有元素就返回null |
一直阻塞 |
當阻塞隊列滿時,生產者線程繼續往隊列裏put元素,隊列會一直阻塞,直到put或響應中斷退出 當阻塞隊列爲空時,消費者線程試圖從隊列裏take元素,隊列會一直阻塞消費者線程,直到隊列可用 |
超時退出 |
當阻塞隊列滿時,隊列會阻塞生產者線程一定時間,超過時間後線程會退出 |
隊列滿時add
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(1);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
隊列空時remove
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(1);
//先進先出,清除最先進入的元素
blockingQueue.remove();
隊列滿時offer
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(1);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
隊列空時poll
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(1);
System.out.println(blockingQueue.poll());
隊列滿時put
static BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(1);
創建兩個線程分別爲生產者和消費者
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("開始");
blockingQueue.put("a");
blockingQueue.put("b");
System.out.println("結束");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
},"生產者").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10_000);
blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
},"消費者").start();
上面的例子中,我們可以看到的現象是生產者線程打印”開始“到打印”結束“之間經歷了10秒。
這是因爲當生產者線程向隊列中put一個b元素時,由於隊列已滿,因此發生了阻塞,
此時,進過10秒的休眠的消費者線程從線程中取出a元素,隊列未滿,生產者線程被喚醒,將b放入隊列,程序結束。
SynchronousQueue隊列
SynchronousQueue沒有容量。
與其他BlockingQueue不同,SynchronousQueue是一個不存儲元素的BlockingQueue。
每一個put操作必須要等待一個take操作,否則不能繼續添加元素,反之亦然。
static BlockingQueue<String> blockingQueue=new SynchronousQueue();
new Thread(new Runnable() {
@Override
public void run() {
try {
blockingQueue.put("a");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
},"生產者").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
},"消費者").start();
只執行put或者只執行take方法,線程都會處於阻塞狀態,因此put操作必須配合take操作。