阻塞隊列 BlockingQueue

阻塞隊列 BlockingQueue

BlockingQueue用法

  • BlockingQueue 通常用于一個線程生產對象,而另外一個線程消費這些對象的場景。下圖是
    對這個原理的闡述:

  • 一個線程將會持續生產新對象并將其插入到隊列之中,直到隊列達到它所能容納的臨界點。
    也就是說,它是有限的。如果該阻塞隊列到達了其臨界點,負責生產的線程將會在往里邊插
    入新對象時發生阻塞。它會一直處于阻塞之中,直到負責消費的線程從隊列中拿走一個對象。
    負責消費的線程將會一直從該阻塞隊列中拿出對象。如果消費線程嘗試去從一個空的隊列中
    提取對象的話,這個消費線程將會處于阻塞之中,直到一個生產線程把一個對象丟進隊列。

BlockingQueue 的方法

  • BlockingQueue 具有 4 組不同的方法用于插入、移除以及對隊列中的元素進行檢查。如果請求的操作不能得到立即執行的話,每個方法的表現也不同。這些方法如下:
/ 拋異常 特定值 阻塞 超時
插入 add(o) offer(o) put(o) offer(o, timeout, TimeUnit)
移出 remove() poll() take() poll(timeout, timeunit)
檢查 element() peek()
  • 四組不同的行為方式解釋:
  1. 拋異常:如果試圖的操作無法立即執行,拋一個異常。
  2. 特定值:如果試圖的操作無法立即執行,返回一個特定的值(常常是 true / false)。
  3. 阻塞:如果試圖的操作無法立即執行,該方法調用將會發生阻塞,直到能夠執行。
  4. 超時:如果試圖的操作無法立即執行,該方法調用將會發生阻塞,直到能夠執行,但等待時間不會超過給定值。返回一個特定值以告知該操作是否成功(典型的是 true / false)。
  • 無法向一個 BlockingQueue 中插入 null。如果你試圖插入 null,BlockingQueue 將會拋出一個 NullPointerException。

方法詳解(只分析 ArrayBlockingQueue 這一種實現,其他的類似)

  • 插入數據方法:add(o)、offer(o)、put(o)、offer(o, timeout, TimeUnit)

    • void put(E e) throws InterruptedException; :隊列的容量已滿時,線程會一直阻塞

      • public void put(E e) throws InterruptedException {
          checkNotNull(e);
          final ReentrantLock lock = this.lock;
          lock.lockInterruptibly();
          try {
            while (count == items.length) // 當隊列的容量已滿時,線程會一直阻塞著
              notFull.await();
            insert(e);     // 會在插入方法中調用 notEmpty.signal(); 喚醒阻塞的take方法線程
          } finally {
            lock.unlock();
          }
        }
        
    • boolean offer(E e); 試著往隊列中插入值,不管隊列滿沒滿,都會返回結果

      • public boolean offer(E e) {
          checkNotNull(e);
          final ReentrantLock lock = this.lock;
          lock.lock();
          try {
            if (count == items.length) // 當隊列已滿時,直接返回false
              return false;
            else {
              insert(e);
              return true;
            }
          } finally {
            lock.unlock();
          }
        }
        
    • boolean add(E e); add方法實際上是AbstractQueue 中的方法,里面的實現是先調用上述的 offer 方法,當 offer 方法返回為 true 是,直接返回true, 如果是返回 false 是,直接拋出異常 IllegalStateException Queue full

      • public boolean add(E e) {
          if (offer(e)) // 直接調用 offer 方法
            return true;
          else // 如果 offer 返回為 false, 那么會拋出  Queue full 異常
            throw new IllegalStateException("Queue full");
        }
        
    • boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;offer(o)方法相似,會先判斷 隊列是否已滿,如果隊列已滿,會循環等待傳入的時間,當超過設置的時間后,還是無空閑位置。會直接返回一個false。

      • public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
          checkNotNull(e);
          long nanos = unit.toNanos(timeout);
          final ReentrantLock lock = this.lock;
          lock.lockInterruptibly();
          try {
            while (count == items.length) { // 輪詢判斷隊列是否已滿
              if (nanos <= 0)  // 如果隊列滿了,判斷設置的等待時間是否超時了,超時了直接返回false
                return false;
              nanos = notFull.awaitNanos(nanos); // 沒超時,調用計算時間方法, 進入下一個循環中繼續判斷
            }
            insert(e);  // 在輪詢等待的時間內,有空閑就立刻插入數據
            return true;
          } finally {
            lock.unlock();
          }
        }
        
  • 移出數據的方法:remove()、 remove(o)、poll()、take()、 poll(timeout, TimeUnit)

    • E poll(); 獲取隊列頭的數據,并刪除該數據(將該位置數據置為null)。如果隊列已經空了,那么直接返回 null

      • public E poll() {
          final ReentrantLock lock = this.lock;
          lock.lock();
          try {
            return (count == 0) ? null : extract(); // 判斷隊列是否為空,為空直接返回null
          } finally {
            lock.unlock();
          }
        }
        
        // 如果隊列不為空,調用extract() 方法, 這個方法調用必須獲取鎖
        /**
         * Extracts element at current take position, advances, and signals.
         * Call only when holding lock.
         */
        private E extract() {
          final Object[] items = this.items;
          E x = this.<E>cast(items[takeIndex]);  // 獲取當前位置的元素(隊列頭)
          items[takeIndex] = null;              // 然后把該位置置為null
          takeIndex = inc(takeIndex);           //  把索引向前推進
          --count;                              // 元素容量計數也減少
          notFull.signal();                     // 通知其他線程,可以進行put操作了
          return x;                             // 返回結果
        }
        
    • E poll(long timeout, TimeUnit unit) throws InterruptedException; 判斷隊列是否為空,如果為空等待設置的時間,等待時間超過設置的時間。直接返回null。

      • public E poll(long timeout, TimeUnit unit) throws InterruptedException {
          long nanos = unit.toNanos(timeout);
          final ReentrantLock lock = this.lock;
          lock.lockInterruptibly();
          try {
            while (count == 0) {   // 判斷隊列是否為空
              if (nanos <= 0)       // 如果為空,判斷是否等待時間已耗盡
                return null;        // 耗盡,直接返回null 
              nanos = notEmpty.awaitNanos(nanos); // 沒耗盡,調用計算時間方法進入下一個循環
            }
            return extract();  // 隊列不為空,直接調用獲取數據的方法
          } finally {
            lock.unlock();
          }
        }
        
    • E take() throws InterruptedException; take 方法,如果隊列為空,會進入阻塞狀態

      • public E take() throws InterruptedException {
          final ReentrantLock lock = this.lock;
          lock.lockInterruptibly();
          try {
            while (count == 0)   //當隊列中沒有元素時,會進入阻塞狀態等待喚醒
              notEmpty.await();  
            return extract();
          } finally {
            lock.unlock();
          }
        }
        
    • E remove(); 實際是調用 poll() 方法獲取隊列頭對象并刪除,如果返回的值不為null,那么久直接返回。如果為null 就拋出 NoSuchElementException

      • public E remove() {
          E x = poll();    // 移出隊列頭對象
          if (x != null)
            return x;
          else
            throw new NoSuchElementException();  // 如果返回的值是null 直接拋出異常信息
        }
        
    • boolean remove(Object o); 該方法不管有沒有獲取到隊列頭對象,都會返回一個結果值。不會拋出異常信息。

    • public boolean remove(Object o) {
        if (o == null) return false;
        final Object[] items = this.items;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
          for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) {  //遍歷隊列
            if (o.equals(items[i])) {   // 判斷對象是否相等
              removeAt(i);
              return true;
            }
          }
          return false;     // 如果沒有相等的對象返回false
        } finally {
          lock.unlock();
        }
      }
      
      
      void removeAt(int i) {
        final Object[] items = this.items;
        // if removing front item, just advance
        if (i == takeIndex) {   // 如果需要移出的對象就是當前的隊列頭,直接置為null且索引后移
          items[takeIndex] = null;
          takeIndex = inc(takeIndex);
        } else {
          // slide over all others up through putIndex.
          for (;;) {  
            int nexti = inc(i);        // 需要移出元素索引的后一個為 nexti
            if (nexti != putIndex) {  // 當下一個索引不等于下一個添加元素的位置, 首次移出時ArrayBlockingQueue 的 putIndex 為0, 隊列頭。移除后為移出的索引位置
              items[i] = items[nexti];  // 后一個元素覆蓋前一個元素
              i = nexti;               // 繼續向后推進
            } else {
              items[i] = null;   // 把最后一個元素置為null
              putIndex = i;      // 最新添加元素的位置 putIndex 為 i
              break;
            }
          }
        }
        --count;
        notFull.signal();           
      }
      
  • 檢查數據的方法: element()、peek()

    • E peek(); 返回隊列頭的元素,但是不刪除。如果隊列為空返回null

      • public E peek() {
          final ReentrantLock lock = this.lock;
          lock.lock();
          try {
            return (count == 0) ? null : itemAt(takeIndex);  // 返回隊列頭位置元素
          } finally {
            lock.unlock();
          }
        }
        
        final E itemAt(int i) {
          return this.<E>cast(items[i]);   // 直接返回隊列頭元素,不做刪除操作
        }
        
    • E element(); 返回隊列頭元素,如果為 null 拋出 NoSuchElementException 異常, 調用的實際上是 peek() 方法。

      • public E element() {
          E x = peek();
          if (x != null)
            return x;
          else
            throw new NoSuchElementException();
        }
        
  • 其他方法: contains(o)、remainingCapacity()、drainTo(Collection<? super E> c)、drainTo(Collection<? super E> c, int maxElements);

    • public boolean contains(Object o); 使用迭代器遍歷元素判斷是否包含Object,返回true or false

    • int remainingCapacity(); 返回剩余容量大小,如果是ArrayBlockingQueue 那么就是數組容量的大小減去已經存在元素的個數值。

    • int drainTo(Collection<? super E> c); 把隊列全部轉換成 Collection 類,并且清空隊列自身。

    • int drainTo(Collection<? super E> c, int maxElements); 從隊列頭部開始,轉換并清空 maxElements 個元素成 Collection 類

      ?

待后續的其他隊列實現類粗略講解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 阻塞隊列與普通隊列的區別在于,當隊列是空的時,從隊列中獲取元素的操作將會被阻塞,或者當隊列是滿時,往隊列里添加元素...
    Maxi_Mao閱讀 411評論 0 0
  • 阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作是:在隊列為空時,獲取元素的線...
    端木軒閱讀 1,022評論 0 2
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,767評論 18 399
  • 該隨筆內容完全引自http://wsmajunfeng.iteye.com/blog/1629354一. 前言在新...
    抓兔子的貓閱讀 507評論 0 11
  • 這車廂 如此陰冷 奔馳在荒涼的高原上 夜 鳥獸不鳴 只余軌聲滾滾 我的肩膀受傷了 驟疼起伏 恰配合著鐵軌的咣當 黯...
    惑多惑少閱讀 157評論 0 0