併發隊列學習
併發隊列
1 什麼是併發隊列:
- 消息隊列很多人知道:消息隊列是分佈式系統中重要的組件,是系統與系統直接的通信
- 併發隊列是什麼:併發隊列多個線程以有次序共享數據的重要組件
2 併發隊列和併發集合的區別:
那就有可能要說了,我們併發集合不是也可以實現多線程之間的數據共享嗎,其實也是有區別的:
- 隊列遵循“先進先出”的規則,可以想象成排隊檢票,隊列一般用來解決大數據量採集處理和顯示的。
- 併發集合就是在多個線程中共享數據的
2 併發隊列介紹
在併發隊列上JDK提供了Queue接口,一個是以Queue接口下的BlockingQueue接口爲代表的阻塞隊列,另一個是高性能(無堵塞)隊列。
3 阻塞隊列和非阻塞隊列區別
- 當隊列阻塞隊列爲空的時,從隊列中獲取元素的操作將會被阻塞。
- 或者當阻塞隊列是滿時,往隊列裏添加元素的操作會被阻塞。
- 或者試圖從空的阻塞隊列中獲取元素的線程將會被阻塞,直到其他的線程往空的隊列插入新的元素。
- 試圖往已滿的阻塞隊列中添加新元素的線程同樣也會被阻塞,直到其他的線程使隊列重新變得空閒起來
4 Java常用的併發隊列:
其實判斷是否是堵塞隊列很簡單,只要繼承了BlockingQueue的都是堵塞隊列
4.1 JDK11的Queue子父關係:
AbstractQueue(翻譯:抽象隊列):實現了AbstractQueue的不一定是堵塞或者非堵塞列隊
BlockingQueue(翻譯:堵塞隊列):實現了BlockingQueue接口的一定是堵塞列隊
5 常用併發列隊的介紹:
- 非堵塞隊列:
1.ArrayDeque, (數組雙端隊列)
2.PriorityQueue, (優先級隊列)
3.ConcurrentLinkedQueue, (基於鏈表的併發隊列) - 堵塞隊列:
1.DelayQueue, (基於時間優先級的隊列,延期阻塞隊列)
2.ArrayBlockingQueue, (基於數組的併發阻塞隊列)
3.LinkedBlockingQueue, (基於鏈表的FIFO阻塞隊列)
4.LinkedBlockingDeque, (基於鏈表的FIFO雙端阻塞隊列)
5.PriorityBlockingQueue, (帶優先級的無界阻塞隊列)
6.SynchronousQueue (併發同步阻塞隊列)
6 常用非堵塞隊列的使用:
6.1 ArrayDeque
ArrayDeque (非堵塞隊列)是JDK容器中的一個雙端隊列實現,內部使用數組進行元素存儲,不允許存儲null值,可以高效的進行元素查找和尾部插入取出,是用作隊列、雙端隊列、棧的絕佳選擇,性能比LinkedList還要好。
ArrayDeque的重要方法:
add(E e) 在隊列尾部添加一個元素
offer(E e) 在隊列尾部添加一個元素,並返回是否成功
remove() 刪除隊列中第一個元素,並返回該元素的值,如果元素爲null,將拋出異常(其實底層調用的是removeFirst())
poll() 刪除隊列中第一個元素,並返回該元素的值,如果元素爲null,將返回null(其實調用的是pollFirst())
element() 獲取第一個元素,如果沒有將拋出異常
peek() 獲取第一個元素,如果返回null
ArrayDeque代碼示例:
package com.lijie;
import java.util.ArrayDeque;
public class Test01 {
public static void main(String[] args) {
ArrayDeque q = new ArrayDeque();
q.add("張三");
q.offer("李四");
q.offer("小紅");
//從頭獲取元素,刪除該元素
System.out.println(q.poll());
//從頭獲取元素,不刪除該元素
System.out.println(q.peek());
//從頭獲取元素,不刪除該元素
System.out.println(q.peek());
//獲取總長度
System.out.println(q.size());
}
}
6.2 ConcurrentLinkedQueue
ConcurrentLinkedQueue (非堵塞隊列): 是一個適用於高併發場景下的隊列,通過無鎖的方式,實現了高併發狀態下的高性能。ConcurrentLinkedQueue的性能要好於BlockingQueue接口,它是一個基於鏈接節點的無界線程安全隊列。該隊列的元素遵循先進先出的原則。該隊列不允許null元素。
ConcurrentLinkedQueue重要方法:
add() 和offer() 都是是添加元素方法
poll() 是提取元素,提取完刪除元素方法
peek() 是提取元素,提取完不刪除方法
ConcurrentLinkedQueue代碼示例:
package com.lijie;
import java.util.concurrent.ConcurrentLinkedDeque;
public class Test02 {
public static void main(String[] args) {
ConcurrentLinkedDeque q = new ConcurrentLinkedDeque();
q.add("張三");
q.offer("李四");
q.offer("小紅");
//從頭獲取元素,刪除該元素
System.out.println(q.poll());
//從頭獲取元素,不刪除該元素
System.out.println(q.peek());
//從頭獲取元素,不刪除該元素
System.out.println(q.peek());
//獲取總長度
System.out.println(q.size());
}
}
6.3 PriorityQueue
PriorityQueue (非堵塞隊列) 一個基於優先級的無界優先級隊列。優先級隊列的元素按照其自然順序進行排序,或者根據構造隊列時提供的 Comparator 進行排序,具體取決於所使用的構造方法。該隊列不允許使用 null 元素也不允許插入不可比較的對象
PriorityQueue重要方法:
peek()//返回隊首元素
poll()//返回隊首元素,隊首元素出隊列
add()//添加元素
size()//返回隊列元素個數
isEmpty()//判斷隊列是否爲空,爲空返回true,不空返回false
PriorityQueue代碼示例:
package com.lijie;
import java.util.PriorityQueue;
public class Test03 {
public static void main(String[] args) {
PriorityQueue<String> q = new PriorityQueue<String>();
//入列
q.offer("1");
q.offer("5");
q.offer("2");
q.offer("3");
q.offer("4");
//出列
System.out.println(q.poll()); // 1
System.out.println(q.poll()); // 2
System.out.println(q.poll()); // 3
System.out.println(q.poll()); // 4
System.out.println(q.poll()); // 5
}
}
觀察打印結果, 入列:15234, 出列是12345, 也是說出列時他自己做了相關的判斷
PriorityQueue自定義代碼比較器示例:
package com.lijie;
import java.util.Comparator;
import java.util.PriorityQueue;
public class Test04 {
//自定義比較器,降序排列
static Comparator<Integer> cmp = new Comparator<Integer>() {
public int compare(Integer e1, Integer e2) {
return e2 - e1;
}
};
public static void main(String[] args) {
//構造參數添加比較器
PriorityQueue<Integer> q = new PriorityQueue<>(cmp);
//入列
q.offer(1);
q.offer(2);
q.offer(3);
q.offer(4);
q.offer(5);
//出列
System.out.println(q.poll()); // 5
System.out.println(q.poll()); // 4
System.out.println(q.poll()); // 3
System.out.println(q.poll()); // 2
System.out.println(q.poll()); // 1
}
}
觀察打印結果, 入列:12345, 出列是54321, 也是說出列時他自己做了相關的判斷
7 常用阻塞列隊的使用
7.1 ArrayBlockingQueue
ArrayBlockingQueue是一個有邊界的阻塞隊列,它的內部實現是一個數組。有邊界的意思是它的容量是有限的,我們必須在其初始化的時候指定它的容量大小,容量大小一旦指定就不可改變。ArrayBlockingQueue是以先進先出的方式存儲數據
ArrayBlockingQueue重要方法:
add(): 在不超出隊列長度的情況下插入元素,可以立即執行,成功返回true,如果隊列滿了就拋出異常。
offer(): 在不超出隊列長度的情況下插入元素的時候則可以立即在隊列的尾部插入指定元素,成功時返回true,如果此隊列已滿,則返回false。
put(): 插入元素的時候,如果隊列滿了就進行等待,直到隊列可用。
remove():底層是用到了poll()方法,檢索並且刪除返回隊列頭的元素,與poll()方法不同的是,元素沒有是進行拋異常NoSuchElementException。
poll(): 檢索並且刪除返回隊列頭的元素,有就返回沒有就返回null。
take(): 檢索並且刪除返回隊列頭的元素,如果元素沒有會一直等待,有就返回。
peek(): 檢索但不移除此隊列的頭部;如果此隊列爲空,則返回null。返回頭部元素。
ArrayBlockingQueue代碼示例:
package com.lijie;
import java.util.concurrent.ArrayBlockingQueue;
public class Test05 {
public static void main(String[] args){
ArrayBlockingQueue<String> arrays = new ArrayBlockingQueue<String>(3);
arrays.add("李四");
arrays.add("張三");
arrays.add("王五");
arrays.add("小紅");
arrays.add("校長");
//會抱錯,在未消費之前不能添加列隊了,列隊已經排滿,
}
}
7.2 LinkedBlockingQueue
LinkedBlockingQueue阻塞隊列大小的配置是可選的,如果我們初始化時指定一個大小,它就是有邊界的,如果不指定,它就是無邊界的。說是無邊界,其實是採用了默認大小爲Integer.MAX_VALUE的容量 。它的內部實現是一個鏈表。
和ArrayBlockingQueue一樣,LinkedBlockingQueue 也是以先進先出的方式存儲數據。
LinkedBlockingQueue的重要方法:
add(): 在不超出隊列長度的情況下插入元素,可以立即執行,成功返回true,如果隊列滿了就拋出異常。
offer(): 在不超出隊列長度的情況下插入元素的時候則可以立即在隊列的尾部插入指定元素,成功時返回true,如果此隊列已滿,則返回false。
put(): 插入元素的時候,如果隊列滿了就進行等待,直到隊列可用。
take(): 從隊列中獲取值,如果隊列中沒有值,線程會一直阻塞,直到隊列中有值,並且該方法取得了該值。
poll(long timeout, TimeUnit unit): 在給定的時間裏,從隊列中獲取值,如果沒有取到會拋出異常。
remainingCapacity():獲取隊列中剩餘的空間。
remove(Object o): 從隊列中移除指定的值。
contains(Object o): 判斷隊列中是否擁有該值。
drainTo(Collection c): 將隊列中值,全部移除,併發設置到給定的集合中。
LinkedBlockingQueue代碼示例:
package com.lijie;
import java.util.concurrent.LinkedBlockingQueue;
public class Test06 {
public static void main(String[] args){
LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue(3);
linkedBlockingQueue.add("張三");
linkedBlockingQueue.add("李四");
linkedBlockingQueue.add("王五");
System.out.println(linkedBlockingQueue.size());
}
}
8 使用BlockingQueue模擬生產者與消費者代碼示例:
package com.lijie;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockingQueueTest {
//final成員變量表示常量,只能被賦值一次,賦值後值不再改變。
private static final int queueSize = 5;
private static final ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(queueSize);
private static final int produceSpeed = 300;//生產速度
private static final int consumeSpeed = 500;//消費速度
//生產者
public static class Producer implements Runnable {
public void run() {
while (true) {
try {
System.out.println("老闆準備炸油條了,架子上還能放:" + (queueSize - queue.size()) + "根油條");
queue.put("1根油條");
System.out.println("老闆炸好了1根油條,架子上還能放:" + (queueSize - queue.size()) + "根油條");
Thread.sleep(produceSpeed);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消費者
public static class Consumer implements Runnable {
public void run() {
while (true) {
try {
System.out.println("A 準備買油條了,架子上還剩" + queue.size() + "根油條");
queue.take();
System.out.println("A 買到1根油條,架子上還剩" + queue.size() + "根油條");
Thread.sleep(consumeSpeed);
System.out.println("B 準備買油條了,架子上還剩" + queue.size() + "根油條");
queue.take();
System.out.println("B 買到1根油條,架子上還剩" + queue.size() + "根油條");
Thread.sleep(consumeSpeed);
System.out.println("C 準備買油條了,架子上還剩" + queue.size() + "根油條");
queue.take();
System.out.println("C 買到1根油條,架子上還剩" + queue.size() + "根油條");
Thread.sleep(consumeSpeed);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread producer = new Thread(new Producer());
Thread consumer = new Thread(new Consumer());
producer.start();
consumer.start();
}
}