天天看點

多線程學習(七)阻塞隊列阻塞隊列

目錄

  • 阻塞隊列
    • 應用場景
      • 模拟一個場景
      • 總結
    • JUC中的阻塞隊列
    • ArrayBlockingQueue 原理分析

阻塞隊列

  • 基本概念:阻塞隊列(BlockingQueue) 是一個支援兩個附加操作的隊列。這兩個附加的操作是:在隊列為空時,擷取元素的線程會等待隊列變為非空。當隊列滿時,存儲元素的線程會等待隊列可用。阻塞隊列常用于生産者和消費者的場景,生産者是往隊列裡添加元素的線程,消費者是從隊列裡拿元素的線程。阻塞隊列就是生産者存放元素的容器,而消費者也隻從容器裡拿元素。

應用場景

模拟一個場景

  • 場景:當使用者注冊的時候,不僅要注冊也要發送獎勵金,根據這個操作進行模拟
  • 原本的做法:
    多線程學習(七)阻塞隊列阻塞隊列
    • 缺點:
      • 性能:在注冊這個環節裡面,假如添加使用者需要花費 1 秒鐘,增加積分需要花費 1 秒鐘,那麼整個注冊結果的傳回就可能需要大于22秒,雖然影響不是很大,但是在量比較大的時候,我們也需要做一些優化。
      • 代碼耦合:添加使用者和增加積分,可以認為是兩個操作,也就是說,增加積分并不是注冊必須要具備的功能,但是一旦增加積分這個邏輯出現異常,就會導緻注冊失敗。這種耦合在程式設計的時候是一定要規避的。
  • 而加入阻塞隊列的做法:通過異步的方式,實作獎勵金發放的功能,減少使用者時常
    多線程學習(七)阻塞隊列阻塞隊列

總結

阻塞隊列這塊的應用場景,比較多的仍然是對于生産者消費者場景的應用,但是由于分布式架構的普及,是的大家更多的關注在分布式消息隊列上。 是以其實如果把阻塞隊列比作成分布式消息隊列的話,那麼所謂的生産者和消費者其實就是基于阻塞隊列的解耦。另外,阻塞隊列是一個fifo 的隊列,是以對于希望線上程級别需要實作對目标服務的順序通路的場景中,也可以使用

JUC中的阻塞隊列

  • JUC中有七種阻塞隊列
  1. ArrayBlockingQueue:數組實作的有界阻塞隊列, 此隊列按照先進先出(FIFO)的原則對元素進行排序。
  2. LinkedBlockingQueue:連結清單實作的有界阻塞隊列, 此隊列的預設和最大長度為Integer.MAX_VALUE。此隊列按照先進先出的原則對元素進行排序
  3. PriorityBlockingQueue:支援優先級排序的無界阻塞隊列, 預設情況下元素采取自然順序升序排列。也可以自定義類實作compareTo()方法來指定元素排序規則,或者初始化PriorityBlockingQueue時,指定構造參數Comparator來對元素進行排序。
  4. DelayQueue:優先級隊列實作的無界阻塞隊列
  5. SynchronousQueue:不存儲元素的阻塞隊列, 每一個put操作必須等待一個take操作,否則不能繼續添加元素。
  6. LinkedTransferQueue:連結清單實作的無界阻塞隊列
  7. LinkedBlockingDeque:連結清單實作的雙向阻塞隊列

ArrayBlockingQueue 原理分析

  1. 構造方法:構造阻塞隊列的方法,有三個
    • 第一個傳遞 capacity,表示數組的長度,也就是隊列的長度
      多線程學習(七)阻塞隊列阻塞隊列
    • 第二個傳遞 capacity 和 fair,fair 表示 :表示是否為公平的阻塞隊列,預設情況下構造的是非公平的阻塞隊列。
      多線程學習(七)阻塞隊列阻塞隊列
    • 第三個傳遞 capacity ,fair 和 Collection<? extends E> c,提供了接收一個幾個作為資料初始化的方法
      多線程學習(七)阻塞隊列阻塞隊列
  2. add():添加元素,主要還是調用父類的add()方法
    多線程學習(七)阻塞隊列阻塞隊列
    • 父類的 add()方法
      多線程學習(七)阻塞隊列阻塞隊列
    • 主要還是判斷offer()方法是否true,也就是通過offer()方法進行添加
      多線程學習(七)阻塞隊列阻塞隊列
      • 第一步設定重入鎖
      • 第二步鎖上
      • 第三步進行判斷,如果超過數組大小就傳回false
      • 第四步如果數組中有位置就進行 enqueue()方法
        多線程學習(七)阻塞隊列阻塞隊列
        • 第一步拿到數組
        • 第二步将資料放入對應下标的數組
        • 第三步判斷是否到了數組的最大值
        • 第四步将隊列中的元素加1
        • 第五步喚醒處于等待狀态下的線程,表示目前隊列中的元素不為空,如果存在消費者線程阻塞,就可以開始取出元素
      • 第五步傳回true
      • 第六步執行finally 将鎖釋放
  3. put:put 方法和 add 方法功能一樣,差異是 put 方法如果隊列滿了,會阻塞。
    多線程學習(七)阻塞隊列阻塞隊列
    • 第一步判斷這個元素是否存在
    • 第二步加入重入鎖
    • 第三步獲得鎖,但是lock的差別是,這個方法優先允許在等待時由其他線程調用等待線程的interrupt方法來中斷等待直接傳回。而lock方法是嘗試獲得鎖成功後才響應中斷。
    • 第四步如果隊列滿了就開始循環等待,也就是阻塞
    • 第五步阻塞完成或者隊列為空就添加資料
    • 第六步釋放鎖
  4. take:方法是一種阻塞擷取隊列中元素的方法,它的實作原理很簡單,有就删除沒有就阻塞,注意這個阻塞是可以中斷的,如果隊列沒有資料那麼就加入 notEmpty 條件隊列等待 有資料就直接取走,方法結束,如果有新的 put 線程添加了資料,那麼 put 操作将會喚醒 take 線程,執行 take 操作。如果隊列中添加了元素,那麼這個時候,會在enqueue 中調用 notempty .signal 喚醒 take 線程來獲得元素
    多線程學習(七)阻塞隊列阻塞隊列
    • 第一步 擷取重入鎖
    • 第二步 獲得鎖,這個鎖是可以中斷的
    • 第三步 如果隊列沒有元素,就阻塞
    • 第四步 如果隊列有元素就執行 dequeue()方法,這個是出隊列的方法,主要是删除隊列頭部的元素并發傳回給用戶端
      多線程學習(七)阻塞隊列阻塞隊列
      • 第一步 擷取元素,預設為0
      • 第二步 将這個位置的數組元素設定為null
      • 第三步 如果索引大于隊列最大值,就設定為0,相當于将索引從隊尾移到隊首
      • 第四步 隊列元素數量減一
      • 第五步 同時将資料更新到疊代器中
      • 第六步 喚醒因為隊列滿了的線程
      • 第七步 傳回取到的元素
    • 第五步 釋放鎖
  5. remove:方法是移除一個指定元素。
    多線程學習(七)阻塞隊列阻塞隊列
    • 第一步 如果傳入的元素為空,就傳回 false
    • 第二步 獲得重入鎖
    • 第三步 判斷隊列中有沒有元素
    • 第四步 進行循環查找,找到元素就删除并且傳回 true
    • 第五步 釋放鎖