隊列是一種很是實用的數據結構,相似於生活中發排隊,可應用於生活,開發中各個方面,好比共享打印機(先請求先打印),消息隊列。你想知道他們是怎麼工做的麼。那就來一塊兒學習一下隊列吧html
1.一種先進先出的線性表前端
2.只容許入棧 push()和出棧 pop()java
在後端(稱爲rear)進行插入操做,在前端(稱爲front)進行刪除操做。算法
使得LinkedList實現了該接口,因此使用隊列的時候,通常採用LinkedList。由於LinkedList是雙向鏈表,能夠很方便的實現隊列的全部功能。後端
Queue使用時要儘可能避免Collection的add()和remove()方法,而是要使用offer()來加入元素,使用poll()來獲取並移出元素。它們的優勢是經過返回值能夠判斷成功與否,add()和remove()方法在失敗的時候會拋出異常。 若是要使用前端而不移出該元素,使用
element()或者peek()方法。數組
public class ArrayQueue { //存儲數據的數組
private String[] items; //記錄數組容量
private int n; private int size; //head記錄隊頭索引,tail記錄隊尾索引
private int head = 0; private int tail = 0; //申請一個指定容量的隊列
public ArrayQueue(int capacity){ items = new String[capacity]; n = capacity; } /* * 入隊: * 1.堆滿的時,入隊失敗 * 1.1頻繁出入隊,形成數組使用不連續 * 1.2在入隊的時候,集中觸發進行數據搬移 * 2.在末尾插入數據,注意tail指向隊尾元素的索引+1 */
public boolean enqueue(String item){ //表示隊滿
if(head == 0 && tail == n) return false; //表示須要數據搬移
else if(head != 0 && tail == n){ for (int i = head; i < tail; i++) { items[i-head] = items[i]; } head = 0; tail = tail - head; } //將數據加入隊列
items[tail++] = item; size++; return true; } //出隊:1.隊空時,出隊失敗;2.出隊,head索引+1
public String dequeue(){ String res = null; if(head == tail) return res; res = items[head++]; size--; return res; } }
(代碼來源於姜威)緩存
public class LinkedQueue { //定義一個節點類
private class Node{ String value; Node next; } //記錄隊列元素個數
private int size = 0; //head指向隊頭結點,tail指向隊尾節點
private Node head; private Node tail; //申請一個隊列
public LinkedQueue(){} //入隊
public boolean enqueue(String item){ Node newNode = new Node(); newNode.value = item; if (size == 0) head = newNode; else tail.next = newNode; tail = newNode; size++; return true; } //出隊
public String dequeue(){ String res = null; if(size == 0) return res; if(size == 1) tail = null; res = head.value; head = head.next; size--; return res; } }
(代碼來源於姜威)安全
循環隊列的實現 public class LoopArrayQueue { //存儲數據的數組
private String[] items; //記錄數組容量
private int n; private int size = 0; //head記錄隊頭索引,tail記錄隊尾索引
private int head = 0; private int tail = 0; //申請一個指定容量的隊列
public LoopArrayQueue(int capacity){ items = new String[capacity]; n = capacity; } //入隊:關鍵在於隊滿的條件
public boolean enqueue(String item){ if ((tail + 1) % n == head) return false; items[tail] = item; tail = (tail + 1) % n; size++; return true; } //出隊:關鍵在於隊空的條件
public String dequeue(){ String res = null; if(head == tail) return res; res = items[head]; head = (head + 1) % n; size--; return res; } }
(代碼來源於姜威)數據結構
1)在隊列的基礎上增長阻塞操做,造成了阻塞隊列。
2)阻塞隊列就是在隊列爲空的時候,從隊頭取數據會被阻塞,由於此時尚未數據可取,直到隊列中有了數據才能返回;若是隊列已經滿了,那麼插入數據的操做就會被阻塞,直到隊列中有空閒位置後再插入數據,而後在返回。
3)「生產者-消費者模型」多線程
(圖片來源於王爭)
基於阻塞隊列實現的「生產者-消費者模型」能夠有效地協調生產和消費的速度。
當「生產者」生產數據的速度過快,「消費者」來不及消費時,存儲數據的隊列很快就會滿了,這時生產者就阻塞等待,直到「消費者」消費了數據,「生產者」纔會被喚醒繼續生產。不只如此,基於阻塞隊列,咱們還能夠經過協調「生產者」和「消費者」的個數,來提升數據處理效率,好比配置幾個消費者,來應對一個生產者。
1)在多線程的狀況下,會有多個線程同時操做隊列,這時就會存在線程安全問題。
線程安全問題的隊列就稱爲併發隊列。
2)併發隊列簡單的實現就是在enqueue()、dequeue()方法上加鎖,可是鎖粒度大併發度會比較低,同一時刻僅容許一個存或取操做。
3)基於數組的循環隊列利用CAS原子操做,能夠實現很是高效的併發隊列。這也是循環隊列比鏈式隊列應用更加普遍的緣由。
3.線程池資源枯竭是的處理
在資源有限的場景,當沒有空閒資源時,基本上均可以經過「隊列」這種數據結構來實現請求排隊。
當咱們向固定大小的線程池中請求一個線程時,若是線程池中沒有空閒資源了,這個時候線程池如何處理這個請求?是拒絕請求仍是排隊請求?各類處理策略又是怎麼實現的呢?
兩種處理策略:
非阻塞的處理方式,直接拒絕任務請求
阻塞的處理方式,將請求排隊,等有空閒線程,取出隊列中請求繼續處理
基於鏈表的實現方式,能夠實現一個支持無線排隊的無界隊列,可是可能會致使過多的請求排隊,請求處理的響應時間過長
基於數組的實現的有界隊列,隊列的大小有限,因此線程池中排隊的請求超過隊列大小時,接下來的請求就會被拒絕,這種方式對響應時間敏感的系統,更加合適;
隊列能夠應用在任何有限的資源池中,當沒有空閒資源均可以經過「隊列」來實現請求排隊
1.除了線程池這種池結構會用到隊列排隊請求,還有哪些相似線程池結構或者場景中會用到隊列的排隊請求呢?
在不少偏底層的系統、框架、中間件的開發中,起着關鍵性的做用。好比高性能隊列 Disruptor、Linux 環形緩存,都用到了循環併發隊列;Java concurrent 併發包利用 ArrayBlockingQueue 來實現公平鎖等。
分佈式消息隊列,如 kafka 也是一種隊列
2.今天講到併發隊列,關於如何實現無鎖的併發隊列,網上有不少討論。對這個問題,你怎麼看?
可使用 cas + 數組的方式實現。考慮使用CAS實現無鎖隊列,則在入隊前,獲取tail位置,入隊時比較tail是否發生變化,若是否,則容許入隊,反之,本次入隊失敗。出隊則是獲取head位置,進行cas
相關文章
以上內容爲我的的學習筆記,僅做爲學習交流之用。
歡迎你們關注公衆號,不定時乾貨,只作有價值的輸出
做者:Dawnzhang
出處:http://www.javashuo.com/article/p-ofwpygma-ev.html
版權:本文版權歸做者轉載:歡迎轉載,但未經做者贊成,必須保留此段聲明;必須在文章中給出原文鏈接;不然必究法律責任