1.阻塞隊列有哪些
強烈推薦參考:https://www.cnblogs.com/bjxq-cs88/p/9759571.html
隊列 | 有界性 | 鎖 | 數據結構 |
---|---|---|---|
ArrayBlockingQueue | bounded(有界) | 加鎖 | arrayList |
LinkedBlockingQueue | optionally-bounded | 加鎖 | linkedList |
PriorityBlockingQueue | unbounded | 加鎖 | heap |
DelayQueue | unbounded | 加鎖 | heap |
SynchronousQueue | bounded | 加鎖 | 無 |
LinkedTransferQueue | unbounded | 加鎖 | heap |
LinkedBlockingDeque | unbounded | 無鎖 | heap |
下面分別簡單介紹一下:
- ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)對元素進行排序。
- LinkedBlokcingQueue:是一個基於鏈表結構的阻塞隊列,此隊列按 FIFO(先進先出)對元素進行排序,吞吐量通常要高於 ArrayBlockingQueue。
- SynchronousQueue:是一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於 LinkedBlokcingQueue; 支持公平鎖和非公平鎖。SynchronousQueue的一個使用場景是在線程池裏。Executors.newCachedThreadPool()就使用了SynchronousQueue,這個線程池根據需要(新任務到來時)創建新的線程,如果有空閒線程則會重複使用,線程空閒了60秒後會被回收。
- LinkedTransferQueue: 一個由鏈表結構組成的無界阻塞隊列,相當於其它隊列,LinkedTransferQueue隊列多了transfer和tryTransfer方法。
- LinkedBlockingDeque: 一個由鏈表結構組成的雙向阻塞隊列。隊列頭部和尾部都可以添加和移除元素,多線程併發時,可以將鎖的競爭最多降到一半。
- PriorityBlockingQueue: 一個支持線程優先級排序的無界隊列,默認自然序進行排序,也可以自定義實現compareTo()方法來指定元素排序規則,不能保證同優先級元素的順序。
- DelayQueue: 一個實現PriorityBlockingQueue實現延遲獲取的無界隊列,在創建元素時,可以指定多久才能從隊列中獲取當前元素。只有延時期滿後才能從隊列中獲取元素。(DelayQueue可以運用在以下應用場景:1.緩存系統的設計:可以用DelayQueue保存緩存元素的有效期,使用一個線程循環查詢DelayQueue,一旦能從DelayQueue中獲取元素時,表示緩存有效期到了。2.定時任務調度。使用DelayQueue保存當天將會執行的任務和執行時間,一旦從DelayQueue中獲取到任務就開始執行,從比如TimerQueue就是使用DelayQueue實現的。)
2.什麼是阻塞隊列
-
阻塞隊列,顧名思義,首先它是一個隊列,而一個阻塞隊列在數據結構中所起的作用大致如圖所示:
-
當阻塞隊列是空時,從隊列中獲取元素的操作將會被阻塞。
-
當阻塞隊列是滿時,往隊列裏添加元素的操作將會被阻塞。
-
核心接口
public interface BlockingQueue<E> extends Queue<E> { //將給定元素設置到隊列中,如果設置成功返回true, 否則拋出異常。如果是往限定了長度的隊列中設置值,推薦使用offer()方法。 boolean add(E e); //將給定的元素設置到隊列中,如果設置成功返回true, 否則返回false. e的值不能爲空,否則拋出空指針異常。 boolean offer(E e); //將元素設置到隊列中,如果隊列中沒有多餘的空間,該方法會一直阻塞,直到隊列中有多餘的空間。 void put(E e) throws InterruptedException; //將給定元素在給定的時間內設置到隊列中,如果設置成功返回true, 否則返回false. boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException; //從隊列中獲取值,如果隊列中沒有值,線程會一直阻塞,直到隊列中有值,並且該方法取得了該值。 E take() throws InterruptedException; //在給定的時間裏,從隊列中獲取值,如果沒有取到會拋出異常。 E poll(long timeout, TimeUnit unit) throws InterruptedException; //獲取隊列中剩餘的空間。 int remainingCapacity(); //從隊列中移除指定的值。 boolean remove(Object o); //判斷隊列中是否擁有該值。 public boolean contains(Object o); //將隊列中值,全部移除,併發設置到給定的集合中。 int drainTo(Collection<? super E> c); //指定最多數量限制將隊列中值,全部移除,併發設置到給定的集合中。 int drainTo(Collection<? super E> c, int maxElements); }
-
核心方法
方法\行爲 拋異常 特定的值 阻塞 超時 插入方法 add(o) offer(o) put(o) offer(o, timeout, timeunit) 移除方法 remove() poll() take() poll(timeout, timeunit) 檢查方法 element() peek() 不可用 不可用 -
行爲解釋:
- 拋異常:如果操作不能馬上進行,則拋出異常
- 特定的值:如果操作不能馬上進行,將會返回一個特殊的值,一般是 true 或者 false
- 阻塞:如果操作不能馬上進行,操作會被阻塞
- 超時:如果操作不能馬上進行,操作會被阻塞指定的時間,如果指定時間沒執行,則返回一個特殊值,一般是 true 或者 false
-
插入方法:
- add(E e):添加成功返回true,失敗拋 IllegalStateException 異常
- offer(E e):成功返回 true,如果此隊列已滿,則返回 false
- put(E e):將元素插入此隊列的尾部,如果該隊列已滿,則一直阻塞
-
刪除方法:
- remove(Object o) :移除指定元素,成功返回true,失敗返回false
- poll():獲取並移除此隊列的頭元素,若隊列爲空,則返回 null
- take():獲取並移除此隊列頭元素,若沒有元素則一直阻塞
-
檢查方法:
- element() :獲取但不移除此隊列的頭元素,沒有元素則拋異常
- peek() :獲取但不移除此隊列的頭;若隊列爲空,則返回 null
3.SynchronousQueue
SynchronousQueue,實際上它不是一個真正的隊列,因爲它不會爲隊列中元素維護存儲空間。與其他隊列不同的是,它維護一組線程,這些線程在等待着把元素加入或移出隊列。
public class SynchronousQueueDemo {
public static void main(String[] args) {
SynchronousQueue<Integer> synchronousQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
synchronousQueue.put(1);
Thread.sleep(3000);
synchronousQueue.put(2);
Thread.sleep(3000);
synchronousQueue.put(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
Integer val = synchronousQueue.take();
System.out.println(val);
Integer val2 = synchronousQueue.take();
System.out.println(val2);
Integer val3 = synchronousQueue.take();
System.out.println(val3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
使用場景
- 生產者消費者模式
- 線程池
- 消息中間件
4.synchronized 和 Lock 區別
- 原始結構
- synchronized 是關鍵字屬於 JVM 層面,反應在字節碼上是 monitorenter 和 monitorexit,其底層是通過 monitor 對象來完成,其實 wait/notify 等方法也是依賴 monitor 對象只有在同步快或方法中才能調用 wait/notify 等方法。
- Lock 是具體類(java.util.concurrent.locks.Lock)是 api 層面的鎖。
- 使用方法
- synchronized 不需要用戶手動去釋放鎖,當 synchronized 代碼執行完後系統會自動讓線程釋放對鎖的佔用。
- ReentrantLock 則需要用戶手動的釋放鎖,若沒有主動釋放鎖,可能導致出現死鎖的現象,lock() 和 unlock() 方法需要配合 try/finally 語句來完成。
- 等待是否可中斷
- synchronized 不可中斷,除非拋出異常或者正常運行完成。
- ReentrantLock 可中斷,設置超時方法 tryLock(long timeout, TimeUnit unit),lockInterruptibly() 放代碼塊中,調用 interrupt() 方法可中斷。
- 加鎖是否公平
- synchronized 非公平鎖
- ReentrantLock 默認非公平鎖,構造方法中可以傳入 boolean 值,true 爲公平鎖,false 爲非公平鎖。
- 鎖可以綁定多個 Condition
- synchronized 沒有 Condition。
- ReentrantLock 用來實現分組喚醒需要喚醒的線程們,可以精確喚醒,而不是像 synchronized 要麼隨機喚醒一個線程要麼喚醒全部線程。