六:阻塞隊列的理解

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.什麼是阻塞隊列

img

  • 阻塞隊列,顧名思義,首先它是一個隊列,而一個阻塞隊列在數據結構中所起的作用大致如圖所示:

  • 當阻塞隊列是空時,從隊列中獲取元素的操作將會被阻塞。

  • 當阻塞隊列是滿時,往隊列裏添加元素的操作將會被阻塞。

  • 核心接口

    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 要麼隨機喚醒一個線程要麼喚醒全部線程。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章