<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      用Java如何設計一個阻塞隊列,然后說說ArrayBlockingQueue和LinkedBlockingQueue

      前言

      用Java如何設計一個阻塞隊列,這個問題是在面滴滴的時候被問到的。當時確實沒回答好,只是說了用個List,然后消費者再用個死循環一直去監控list的是否有值,有值的話就處理List里面的內容?;仡^想想,自己真是一個大傻X,也只有我才會這么設計一個阻塞隊列(再說,我這也不是阻塞的隊列)。
      結果自己面試完之后,也沒去總結這部分知識,然后過了一段時間,某教育機構的面試又被問到類似的問題了,只不過是換了一個形式,“請用wait方法和notify方法實現一套有生產者和消費者的這種邏輯”。然后我就又蒙圈了,追悔莫及,為啥我沒有去了解一下這部分知識,所以這次我準備好好總結一下這部分內容。

      具體實現

      如果說實現一個隊列,那么一個LinkedList的這種實現了Queue接口的都可以直接使用,或者自己寫一個先進先出的Array都可以。
      但是要做到阻塞就還需要進行阻塞的實現,就是說當隊列是空時,如果再繼續從隊列中獲取數據,將會被阻塞,直到有新的數據入隊列才停止阻塞;還有當隊列已經滿了(到達設置的最大容量),再往隊列里添加元素的操作也會被阻塞,直到有數據從隊列中被移除。

      這里首先要有一個鎖,保證同時只能有一個線程執行出隊列、同時只能有一個線程執行入隊列。而執行出隊列和入隊列的線程的阻塞和喚醒,是靠wait()方法和notifyAll()方法來實現的。

      代碼實現如下:

      public class MyBlockQueue {
      
          /**
           * 隊列長度默認為10
           */
          private int limit = 10;
          private Queue queue = new LinkedList<>();
      
          /**
           * 初始化隊列容量
           * @param limit 隊列容量
           */
          public MyBlockQueue(int limit){
              this.limit = limit;
          }
      
          /**
           * 入隊列
           * @param object    隊列元素
           * @throws InterruptedException
           */
          public synchronized boolean push(Object object) throws InterruptedException{
              // 如果隊列已滿,再來添加隊列的線程就直接阻塞等待。
              while (this.queue.size() == this.limit){
                  wait();
              }
              // 如果隊列為空了,就喚醒所有阻塞的線程。
              if(this.queue.size() == 0){
                  notifyAll();
              }
              // 入隊
              boolean add = this.queue.offer(object);
              return add;
          }
      
          /**
           * 出隊列
           * @return
           * @throws InterruptedException
           */
          public synchronized Object pop() throws InterruptedException{
              // 如果出隊列時,隊列為空,則阻塞隊列。
              while (this.queue.size() == 0){
                  wait();
              }
              // 如果隊列重新滿了之后,喚醒阻塞的所有線程。
              if(this.queue.size() == this.limit){
                  notifyAll();
              }
              Object poll = this.queue.poll();
              return poll;
          }
      
      }
      

      Java中阻塞隊列的實現

      首先我們先來歸納一下,Java中有哪些已經實現好了的阻塞隊列:

      隊列 描述
      ArrayBlockingQueue 基于數組結構實現的一個有界阻塞隊列
      LinkedBlockingQueue 基于鏈表結構實現的一個有界阻塞隊列
      PriorityBlockingQueue 支持按優先級排序的無界阻塞隊列
      DelayQueue 基于優先級隊列(PriorityBlockingQueue)實現的無界阻塞隊列
      SynchronousQueue 不存儲元素的阻塞隊列
      LinkedTransferQueue 基于鏈表結構實現的一個無界阻塞隊列
      LinkedBlockingDeque 基于鏈表結構實現的一個雙端阻塞隊列

      我們這次主要來看一下ArrayBlockingQueueLinkedBlockingQueue這兩個阻塞隊列。
      在介紹這兩個阻塞隊列時,先普及兩個知識,就是ReentrantLockCondition的幾個方法。因為JDK中的這些阻塞隊列加鎖時基本上都是通過這兩種方式的API來實現的。

      ReentrantLock

      • lock():加鎖操作,如果此時有競爭會進入等待隊列中阻塞直到獲取鎖。
      • lockInterruptibly():加鎖操作,但是優先支持響應中斷。
      • tryLock():嘗試獲取鎖,不等待,獲取成功返回true,獲取不成功直接返回false。
      • tryLock(long timeout, TimeUnit unit):嘗試獲取鎖,在指定的時間內獲取成功返回true,獲取失敗返回false。
      • unlock():釋放鎖。

      Condition

      通常和ReentrantLock一起使用的

      • await():阻塞當前線程,并釋放鎖。
      • signal():喚醒一個等待時間最長的線程。

      ArrayBlockingQueue

      構造方法

      首先來看一下ArrayBlockingQueue的初始化方法
      ArrayBlockingQueue初始化
      ArrayBlockingQueue初始化
      ArrayBlockingQueue是有三個構造方法的,但是都是基于ArrayBlockingQueue(int capacity, boolean fair)來實現的,所以只要了解這一個構造方法即可。
      主要是:

      • 采用數組結構來初始化隊列,并定義隊列長度;
      • 然后創建全局鎖,出隊和入隊時都要先獲取鎖再執行操作;
      • 創建阻塞線程的非空等待隊列;
      • 創建阻塞線程的非滿等待隊列;

      入隊列

      下面來看一下入隊列操作
      ArrayBlockingQueue的put
      ArrayBlockingQueue的offer
      無論put()方法還是offer()方法,在入隊列時都是先加鎖,然后最終入隊列都是調用的enqueue()方法,只不過put方法是阻塞入隊列,就是說如果隊列已滿,入隊列的線程會被阻塞,而offer方法則不會阻塞入隊列不成功的線程,offer執行入隊列不成功的線程直接返回失敗,其實還有一個add方法也是入隊列,和offer方法一直都是非阻塞入隊。

      下面來一下enqueue()方法。
      在這里插入圖片描述
      enqueue()方法其實步驟也不復雜,主要是入隊列操作是從數組的尾部入,然后出隊列是從隊列的頭部出,這樣當隊列滿了的時候,下一次再入隊列時的位置應該從隊列的頭部開始入了。所以才會有重置putIndex的操作。

      如果不能理解可以看下面的圖片,正常隊列未滿時,從數組尾部入隊列,頭部出隊列。
      數組隊列未滿時正常入隊操作
      當隊列滿了之后,入隊列就要從數組頭部位置開始了。
      在這里插入圖片描述

      出隊列

      下面來看一下ArrayBlockingQueue的出隊列方法
      ArrayBlockingQueue的poll方法
      ArrayBlockingQueue的take方法
      我們通過上面兩張源碼的截圖可以看出來,無論是poll()方法還是take()方法,最終出隊列調用的都是dequeue()方法,只不過take()是阻塞的方式出隊列,當隊列為空時直接將出隊列線程阻塞并放到等待隊列中。

      那么dequeue()是如何出隊列的呢?
      ArrayBlockingQueue的dequeue()
      我們通過源碼可知,出隊列是根據出隊列索引takeIndex來決定該出哪一個元素了,如果當前出隊列的元素的索引正好是數組容量的最后一個元素,那么出隊列索引takeIndex也要重新從頭開始記錄了。后面再更新迭代器中的數據,以及喚醒阻塞中的入隊線程。

      還有兩個出隊列的方法remove(Object o)removeAt(final int removeIndex)這兩個方法稍微復雜一些,因為首先要定位到要移除的元素的位置,然后再執行出隊操作,remove最終執行的出隊方法是依賴removeAt(final int removeIndex),而removeAt的出隊操作是定位到要移除的元素位置后,將takeIndex位置的元素替換掉要移除的元素,就完成了出隊操作 。

      LinkedBlockingQueue

      構造方法

      LinkedBlockingQueue的初始化隊列的數據信息時是在構造方法中進行的,但是實現阻塞隊列需要的核心能力是在JVM為對象分配空間時就初始化好了的。
      LinkedBlockingQueue數據初始化
      LinkedBlockingQueue的構造方法

      入隊列

      從初始化數據的時候可以看到,LinkedBlockingQueue是有兩個鎖的,入隊列有入隊列的鎖,出隊列有出隊列的鎖,是兩個獨立的重入鎖。這樣入隊列和出隊列相互對立的處理,大大的提高了隊列的吞吐量。
      LinkedBlockingQueue的put方法
      LinkedBlockingQueue的offer方法
      我們看到LinkedBlockingQueue的入隊列的兩個方法put和offer(其實還有一個add方法,但是具體實現也是調用的offer方法),put方法是阻塞入隊,即當隊列滿了的時候阻塞入隊列的線程,而offer則不是阻塞入隊,入隊列成功即返回true否則返回false。

      這兩個方法底層調用的都是enqueue()方法,我們看一下這個方法具體是怎么執行的入隊列。
      LinkedBlockingQueue的enqueue方法
      enqueue()方法邏輯比較簡單,就是將元素添加到鏈表的尾部。
      LinkedBlockingQueue

      出隊列

      LinkedBlockingQueue的出隊列方法,是先獲取出隊列的takeLock,然后再執行出隊列方法。
      LinkedBlockingQueue的take方法
      LinkedBlockingQueue的poll方法
      take方法和poll方法前者在隊列為空后,會阻塞出隊列的線程,后者poll方法則不會在隊列為空時阻塞出隊列線程,會直接返回null。

      無論是take方法還是poll方法都是調用的dequeue()方法執行的出隊列,那么看一下dequeue()方法的實現吧。一直忘記說了,我這次貼出來的源碼都是JDK1.8版本的。
      LinkedBlockingQueue的dequeue方法
      我們看到dequeue()執行了一個比較繞的邏輯,主要意思是將頭節點后的第一個不為null的節點移除隊列了,并設置了新的頭節點位置。

      我們來仔細拆分一下步驟,就好理解了,初始時,頭節點的值是null(new Node(null))但是next指向的是隊列中的第二個節點。

      • 第一步把head節點會把自己的next節點從指向第二節點,改成指向自己,這樣,本來head節點的值就是null,然后現在next也是一個空節點了,這樣的節點GC的時候就會被優先回收掉了。
      • 第二步把原先head節點的下一個節點的值賦值給head,這樣原先的第二節點就成為了head節點,然后將新head節點的數據返回。
      • 將新head節點的值設置為null,這樣就新的節點的也就和原先的head節點的數據形式一樣了。

      我們可以通過下面圖來更清晰的看一下:
      LinkedBlockingQueue的出隊列
      我們再來看一下出隊列的另一個方法remove。
      LinkedBlockingQueue的remove方法
      執行remove()方法的時候,要將出隊列鎖和入隊列的鎖都加上,這兩個操作要等待remove()方法執行完畢后再操作。為了就是保證在remove()方法尋找指定元素時有入隊和出隊操作導致遍歷操作混亂。

      我們再來看一下unlink()方法,主要還是將元素從鏈表中移除,若移除的元素為last元素,做一些處理等。
      在這里插入圖片描述

      總結

      • 自己實現了阻塞隊列,首先要有鎖來保證入隊列和出隊列的線程在隊列滿和隊列為空時阻塞主入隊列線程和出隊列線程。然后再隊列有空間后喚醒入隊列線程,在隊列有數據時喚醒出隊列線程。
      • ArrayBlockingQueueLinkedBlockingQueue都是有界的阻塞隊列(LinkedBlockingQueue的默認長度為Int的最大值也暫且歸為是有界),ArrayBlockingQueue是通過數據來實現阻塞隊列的,并且是依賴ReentrantLockCondition來進行加鎖的。LinkedBlockingQueue是通過鏈表來實現阻塞隊列的,也是依賴ReentrantLockCondition來完成加鎖的。
      • ArrayBlockingQueue采用的全局唯一鎖,入隊列和出隊列只能有一個操作同時進行,LinkedBlockingQueue入隊列和出隊列分別采用對立的重入鎖,入隊列和出隊列可分開執行,所以吞吐量比ArrayBlockingQueue更高。
      • ArrayBlockingQueue采用數組來實現隊列,執行過程中并不會釋放內存空間,所以需要更多的連續內存;LinkedBlockingQueue雖然不需要大量的聯系內存,但是在并發情況下,會創建和置空大量的對象,很依賴GC的處理效率。
      posted @ 2021-06-16 08:24  紀莫  閱讀(1064)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 久久av无码精品人妻出轨| 亚洲嫩模喷白浆在线观看| 无码内射中文字幕岛国片| 成人亚欧欧美激情在线观看| 国产精品麻豆成人AV电影艾秋 | 亚洲国产成人久久精品app| 天天影视色香欲综合久久| 国产一区二区亚洲一区二区三区| 国产免费一区二区不卡| 国产精品久久精品国产| 日本偷拍自影像视频久久| 午夜在线观看成人av| 国产精品成人中文字幕| 日韩狼人精品在线观看| 边添小泬边狠狠躁视频| 色吊丝免费av一区二区| 欧美成人精品手机在线| 日韩乱码人妻无码中文字幕视频| 国产91精品一区二区亚洲| 亚洲中文无码手机永久| 美女人妻激情乱人伦| 亚洲成人精品综合在线| 我国产码在线观看av哈哈哈网站| 欧美激欧美啪啪片| 中文字幕午夜福利片午夜福利片97| 国产在线无码不卡播放| 成人又黄又爽又色的视频| 亚洲悠悠色综合中文字幕| 一区二区三区国产亚洲网站| 国产h视频在线观看| 国产成人精品a视频一区| 我国产码在线观看av哈哈哈网站| 亚洲av无码国产在丝袜线观看| 日韩区一区二区三区视频| 亚洲美女厕所偷拍美女尿尿| 免费无码成人AV片在线| 绥化市| 日本久久一区二区三区高清| 国产乱码精品一区二区三区四川人 | gogogo高清在线播放免费| 国产一区二区不卡在线看|