<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          圖解 Java 中的 5 大隊列,再也不用擔心排隊的問題了

          共 2331字,需瀏覽 5分鐘

           ·

          2020-11-03 17:26

          我們知道,隊列(Queue)是先進先出(FIFO)的,并且我們可以用數(shù)組、鏈表還有 List 的方式來實現(xiàn)自定義隊列,那么本文我們來系統(tǒng)的學習一下官方是如何實現(xiàn)隊列的。

          Java 中的隊列有很多,例如:ArrayBlockingQueue、LinkedBlockingQueuePriorityQueue、DelayQueue、SynchronousQueue 等,那它們的作用是什么?又是如何分類的呢?

          其實 Java 中的這些隊列可以從不同的維度進行分類,例如可以從阻塞和非阻塞進行分類,也可以從有界和無界進行分類,而本文將從隊列的功能上進行分類,例如:優(yōu)先隊列、普通隊列、雙端隊列、延遲隊列等。


          雖然本文的重點是從功能上對隊列進行解讀,但其它分類也是 Java 中的重要概念,所以我們先來了解一下它們。

          阻塞隊列和非阻塞隊列

          阻塞隊列(Blocking Queue)提供了可阻塞的 puttake 方法,它們與可定時的 offerpoll 是等價的。如果隊列滿了 put 方法會被阻塞等到有空間可用再將元素插入;如果隊列是空的,那么 take 方法也會阻塞,直到有元素可用。當隊列永遠不會被充滿時,put 方法和 take?方法就永遠不會阻塞。


          我們可以從隊列的名稱中知道此隊列是否為阻塞隊列,阻塞隊列中包含 BlockingQueue?關鍵字,比如以下這些:

          • ArrayBlockingQueue
          • LinkedBlockingQueue
          • PriorityBlockingQueue
          • .......

          阻塞隊列功能演示

          接下來我們來演示一下當阻塞隊列的容量滿了之后會怎樣,示例代碼如下:

          import?java.util.Date;
          import?java.util.concurrent.ArrayBlockingQueue;

          public?class?BlockingTest?{
          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????//?創(chuàng)建一個長度為?5?的阻塞隊列
          ????????ArrayBlockingQueue?q1?=?new?ArrayBlockingQueue(5);
          ????????
          ????????//?新創(chuàng)建一個線程執(zhí)行入列
          ????????new?Thread(()?->?{
          ????????????//?循環(huán)?10?次
          ????????????for?(int?i?=?0;?i?10;?i++)?{
          ????????????????try?{
          ????????????????????q1.put(i);
          ????????????????}?catch?(InterruptedException?e)?{
          ????????????????????e.printStackTrace();
          ????????????????}
          ????????????????System.out.println(new?Date()?+?"?|?ArrayBlockingQueue?Size:"?+?q1.size());
          ????????????}
          ????????????System.out.println(new?Date()?+?"?|?For?End.");
          ????????}).start();

          ????????//?新創(chuàng)建一個線程執(zhí)行出列
          ????????new?Thread(()?->?{
          ????????????for?(int?i?=?0;?i?5;?i++)?{
          ????????????????try?{
          ????????????????????//?休眠?1S
          ????????????????????Thread.sleep(1000);
          ????????????????}?catch?(InterruptedException?e)?{
          ????????????????????e.printStackTrace();
          ????????????????}
          ????????????????if?(!q1.isEmpty())?{
          ????????????????????try?{
          ????????????????????????q1.take();?//?出列
          ????????????????????}?catch?(InterruptedException?e)?{
          ????????????????????????e.printStackTrace();
          ????????????????????}
          ????????????????}
          ????????????}
          ????????}).start();
          ????}
          }

          以上代碼的執(zhí)行結果如下:

          Mon Oct 19 20:16:12 CST 2020 | ArrayBlockingQueue Size:1

          Mon Oct 19 20:16:12 CST 2020 | ArrayBlockingQueue Size:2

          Mon Oct 19 20:16:12 CST 2020 | ArrayBlockingQueue Size:3

          Mon Oct 19 20:16:12 CST 2020 | ArrayBlockingQueue Size:4

          Mon Oct 19 20:16:12 CST 2020 | ArrayBlockingQueue Size:5

          Mon Oct 19 20:16:13 CST 2020 | ArrayBlockingQueue Size:5

          Mon Oct 19 20:16:14 CST 2020 | ArrayBlockingQueue Size:5

          Mon Oct 19 20:16:15 CST 2020 | ArrayBlockingQueue Size:5

          Mon Oct 19 20:16:16 CST 2020 | ArrayBlockingQueue Size:5

          Mon Oct 19 20:16:17 CST 2020 | ArrayBlockingQueue Size:5

          Mon Oct 19 20:16:17 CST 2020 | For End.

          從上述結果可以看出,當 ArrayBlockingQueue?隊列滿了之后就會進入阻塞,當過了 1 秒有元素從隊列中移除之后,才會將新的元素入列。

          非阻塞隊列

          非阻塞隊列也就是普通隊列,它的名字中不會包含 BlockingQueue?關鍵字,并且它不會包含 put?和 take?方法,當隊列滿之后如果還有新元素入列會直接返回錯誤,并不會阻塞的等待著添加元素,如下圖所示:

          非阻塞隊列的典型代表是 ConcurrentLinkedQueue?和 PriorityQueue。

          有界隊列和無界隊列

          有界隊列:是指有固定大小的隊列,比如設定了固定大小的 ArrayBlockingQueue,又或者大小為 0 的 SynchronousQueue。

          無界隊列:指的是沒有設置固定大小的隊列,但其實如果沒有設置固定大小也是有默認值的,只不過默認值是 Integer.MAX_VALUE,當然實際的使用中不會有這么大的容量(超過 Integer.MAX_VALUE),所以從使用者的角度來看相當于 “無界”的。


          按功能分類

          接下來就是本文的重點了,我們以功能來劃分一下隊列,它可以被分為:普通隊列、優(yōu)先隊列、雙端隊列、延遲隊列、其他隊列等,接下來我們分別來看。

          1.普通隊列

          普通隊列(Queue)是指實現(xiàn)了先進先出的基本隊列,例如 ArrayBlockingQueue?和 LinkedBlockingQueue,其中 ArrayBlockingQueue 是用數(shù)組實現(xiàn)的普通隊列,如下圖所示:

          LinkedBlockingQueue 是使用鏈表實現(xiàn)的普通隊列,如下圖所示:


          常用方法

          普通隊列中的常用方法有以下這些:

          • offer():添加元素,如果隊列已滿直接返回 false,隊列未滿則直接插入并返回 true;
          • poll():刪除并返回隊頭元素,當隊列為空返回 null;
          • add():添加元素,此方法是對 offer 方法的簡單封裝,如果隊列已滿,拋出 IllegalStateException 異常;
          • remove():直接刪除隊頭元素;
          • put():添加元素,如果隊列已經(jīng)滿,則會阻塞等待插入;
          • take():刪除并返回隊頭元素,當隊列為空,則會阻塞等待;
          • peek():查詢隊頭元素,但不會進行刪除;
          • element():對 peek 方法進行簡單封裝,如果隊頭元素存在則取出并不刪除,如果不存在拋出 NoSuchElementException 異常。

          注意:一般情況下 offer() 和 poll() 方法配合使用,put() 和 take() 阻塞方法配合使用,add() 和 remove() 方法會配合使用,程序中常用的是 offer() 和 poll() 方法,因此這兩個方法比較友好,不會報錯

          接下來我們以 LinkedBlockingQueue 為例,演示一下普通隊列的使用:

          import?java.util.concurrent.LinkedBlockingQueue;

          static?class?LinkedBlockingQueueTest?{
          ????public?static?void?main(String[]?args)?{
          ????????LinkedBlockingQueue?queue?=?new?LinkedBlockingQueue();
          ????????queue.offer("Hello");
          ????????queue.offer("Java");
          ????????queue.offer("中文社群");
          ????????while?(!queue.isEmpty())?{
          ????????????System.out.println(queue.poll());
          ????????}
          ????}
          }

          以上代碼的執(zhí)行結果如下:

          Hello

          Java

          中文社群

          2.雙端隊列

          雙端隊列(Deque)是指隊列的頭部和尾部都可以同時入隊和出隊的數(shù)據(jù)結構,如下圖所示:

          接下來我們來演示一下雙端隊列 LinkedBlockingDeque 的使用:

          import?java.util.concurrent.LinkedBlockingDeque;

          /**
          ??*?雙端隊列示例
          ??*/

          static?class?LinkedBlockingDequeTest?{
          ????public?static?void?main(String[]?args)?{
          ????????//?創(chuàng)建一個雙端隊列
          ????????LinkedBlockingDeque?deque?=?new?LinkedBlockingDeque();
          ????????deque.offer("offer");?//?插入首個元素
          ????????deque.offerFirst("offerFirst");?//?隊頭插入元素
          ????????deque.offerLast("offerLast");?//?隊尾插入元素
          ????????while?(!deque.isEmpty())?{
          ????????????//?從頭遍歷打印
          ????????????System.out.println(deque.poll());
          ????????}
          ????}
          }

          以上代碼的執(zhí)行結果如下:

          offerFirst

          offer

          offerLast

          3.優(yōu)先隊列

          優(yōu)先隊列(PriorityQueue)是一種特殊的隊列,它并不是先進先出的,而是優(yōu)先級高的元素先出隊。

          優(yōu)先隊列是根據(jù)二叉堆實現(xiàn)的,二叉堆的數(shù)據(jù)結構如下圖所示:

          二叉堆分為兩種類型:一種是最大堆一種是最小堆。以上展示的是最大堆,在最大堆中,任意一個父節(jié)點的值都大于等于它左右子節(jié)點的值。

          因為優(yōu)先隊列是基于二叉堆實現(xiàn)的,因此它可以將優(yōu)先級最好的元素先出隊。

          接下來我們來演示一下優(yōu)先隊列的使用:

          import?java.util.PriorityQueue;

          public?class?PriorityQueueTest?{
          ????//?自定義的實體類
          ????static?class?Viper?{
          ????????private?int?id;?//?id
          ????????private?String?name;?//?名稱
          ????????private?int?level;?//?等級

          ????????public?Viper(int?id,?String?name,?int?level)?{
          ????????????this.id?=?id;
          ????????????this.name?=?name;
          ????????????this.level?=?level;
          ????????}

          ????????public?int?getId()?{
          ????????????return?id;
          ????????}

          ????????public?void?setId(int?id)?{
          ????????????this.id?=?id;
          ????????}

          ????????public?String?getName()?{
          ????????????return?name;
          ????????}

          ????????public?void?setName(String?name)?{
          ????????????this.name?=?name;
          ????????}

          ????????public?int?getLevel()?{
          ????????????return?level;
          ????????}

          ????????public?void?setLevel(int?level)?{
          ????????????this.level?=?level;
          ????????}
          ????}
          ????public?static?void?main(String[]?args)?{
          ??PriorityQueue?queue?=?new?PriorityQueue(10,?new?Comparator()?{
          ????????????@Override
          ????????????public?int?compare(Viper?v1,?Viper?v2)?{
          ????????????????//?設置優(yōu)先級規(guī)則(倒序,等級越高權限越大)
          ????????????????return?v2.getLevel()?-?v1.getLevel();
          ????????????}
          ????????});
          ????????//?構建實體類
          ????????Viper?v1?=?new?Viper(1,?"Java",?1);
          ????????Viper?v2?=?new?Viper(2,?"MySQL",?5);
          ????????Viper?v3?=?new?Viper(3,?"Redis",?3);
          ????????//?入列
          ????????queue.offer(v1);
          ????????queue.offer(v2);
          ????????queue.offer(v3);
          ????????while?(!queue.isEmpty())?{
          ????????????//?遍歷名稱
          ????????????Viper?item?=?(Viper)?queue.poll();
          ????????????System.out.println("Name:"?+?item.getName()?+
          ???????????????????????????????" Level:"?+?item.getLevel());
          ????????}
          ????}
          }

          以上代碼的執(zhí)行結果如下:

          Name:MySQL Level:5

          Name:Redis Level:3

          Name:Java Level:1

          從上述結果可以看出,優(yōu)先隊列的出隊是不考慮入隊順序的,它始終遵循的是優(yōu)先級高的元素先出隊

          4.延遲隊列

          延遲隊列(DelayQueue)是基于優(yōu)先隊列 PriorityQueue 實現(xiàn)的,它可以看作是一種以時間為度量單位的優(yōu)先的隊列,當入隊的元素到達指定的延遲時間之后方可出隊。


          我們來演示一下延遲隊列的使用:

          import?lombok.Getter;
          import?lombok.Setter;
          import?java.text.DateFormat;
          import?java.util.Date;
          import?java.util.concurrent.DelayQueue;
          import?java.util.concurrent.Delayed;
          import?java.util.concurrent.TimeUnit;

          public?class?CustomDelayQueue?{
          ????//?延遲消息隊列
          ????private?static?DelayQueue?delayQueue?=?new?DelayQueue();
          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????producer();?//?調(diào)用生產(chǎn)者
          ????????consumer();?//?調(diào)用消費者
          ????}

          ????//?生產(chǎn)者
          ????public?static?void?producer()?{
          ????????//?添加消息
          ????????delayQueue.put(new?MyDelay(1000,?"消息1"));
          ????????delayQueue.put(new?MyDelay(3000,?"消息2"));
          ????}

          ????//?消費者
          ????public?static?void?consumer()?throws?InterruptedException?{
          ????????System.out.println("開始執(zhí)行時間:"?+
          ????????????????DateFormat.getDateTimeInstance().format(new?Date()));
          ????????while?(!delayQueue.isEmpty())?{
          ????????????System.out.println(delayQueue.take());
          ????????}
          ????????System.out.println("結束執(zhí)行時間:"?+
          ????????????????DateFormat.getDateTimeInstance().format(new?Date()));
          ????}

          ????static?class?MyDelay?implements?Delayed?{
          ????????//?延遲截止時間(單位:毫秒)
          ????????long?delayTime?=?System.currentTimeMillis();
          ????????//?借助?lombok?實現(xiàn)
          ????????@Getter
          ????????@Setter
          ????????private?String?msg;

          ????????/**
          ?????????*?初始化
          ?????????*?@param?delayTime?設置延遲執(zhí)行時間
          ?????????*?@param?msg???????執(zhí)行的消息
          ?????????*/

          ????????public?MyDelay(long?delayTime,?String?msg)?{
          ????????????this.delayTime?=?(this.delayTime?+?delayTime);
          ????????????this.msg?=?msg;
          ????????}

          ????????//?獲取剩余時間
          ????????@Override
          ????????public?long?getDelay(TimeUnit?unit)?{
          ????????????return?unit.convert(delayTime?-?System.currentTimeMillis(),?TimeUnit.MILLISECONDS);
          ????????}

          ????????//?隊列里元素的排序依據(jù)
          ????????@Override
          ????????public?int?compareTo(Delayed?o)?{
          ????????????if?(this.getDelay(TimeUnit.MILLISECONDS)?>?o.getDelay(TimeUnit.MILLISECONDS))?{
          ????????????????return?1;
          ????????????}?else?if?(this.getDelay(TimeUnit.MILLISECONDS)?????????????????return?-1;
          ????????????}?else?{
          ????????????????return?0;
          ????????????}
          ????????}
          ????????@Override
          ????????public?String?toString()?{
          ????????????return?this.msg;
          ????????}
          ????}
          }

          以上代碼的執(zhí)行結果如下:

          開始執(zhí)行時間:2020-10-20 20:17:28

          消息1

          消息2

          結束執(zhí)行時間:2020-10-20 20:17:31

          從上述結束執(zhí)行時間和開始執(zhí)行時間可以看出,消息 1 和消息 2 都正常實現(xiàn)了延遲執(zhí)行的功能。

          5.其他隊列

          在 Java 的隊列中有一個比較特殊的隊列 SynchronousQueue,它的特別之處在于它內(nèi)部沒有容器,每次進行 put() 數(shù)據(jù)后(添加數(shù)據(jù)),必須等待另一個線程拿走數(shù)據(jù)后才可以再次添加數(shù)據(jù),它的使用示例如下:

          import?java.util.concurrent.SynchronousQueue;

          public?class?SynchronousQueueTest?{

          ????public?static?void?main(String[]?args)?{
          ????????SynchronousQueue?queue?=?new?SynchronousQueue();

          ????????//?入隊
          ????????new?Thread(()?->?{
          ????????????for?(int?i?=?0;?i?3;?i++)?{
          ????????????????try?{
          ????????????????????System.out.println(new?Date()?+?",元素入隊");
          ????????????????????queue.put("Data?"?+?i);
          ????????????????}?catch?(InterruptedException?e)?{
          ????????????????????e.printStackTrace();
          ????????????????}

          ????????????}
          ????????}).start();

          ????????//?出隊
          ????????new?Thread(()?->?{
          ????????????while?(true)?{
          ????????????????try?{
          ????????????????????Thread.sleep(1000);
          ????????????????????System.out.println(new?Date()?+?",元素出隊:"?+?queue.take());
          ????????????????}?catch?(InterruptedException?e)?{
          ????????????????????e.printStackTrace();
          ????????????????}
          ????????????}
          ????????}).start();
          ????}
          }

          以上代碼的執(zhí)行結果如下:

          Mon Oct 19 21:00:21 CST 2020,元素入隊

          Mon Oct 19 21:00:22 CST 2020,元素出隊:Data 0

          Mon Oct 19 21:00:22 CST 2020,元素入隊

          Mon Oct 19 21:00:23 CST 2020,元素出隊:Data 1

          Mon Oct 19 21:00:23 CST 2020,元素入隊

          Mon Oct 19 21:00:24 CST 2020,元素出隊:Data 2

          從上述結果可以看出,當有一個元素入隊之后,只有等到另一個線程將元素出隊之后,新的元素才能再次入隊。

          總結

          本文講了 Java 中的 5 種隊列:普通隊列、雙端隊列、優(yōu)先隊列、延遲隊列、其他隊列。其中普通隊列的典型代表為 ArrayBlockingQueueLinkedBlockingQueue,雙端隊列的代表為 LinkedBlockingDeque,優(yōu)先隊列的代表為 PriorityQueue,延遲隊列的代表為 DelayQueue,最后還講了內(nèi)部沒有容器的其他隊列 SynchronousQueue.

          ------------------

          公眾號:沉默王二
          這是一枚沉默但有趣的程序員,你知道,他的文章風趣幽默,讀起來就好像花錢一樣爽快。
          長按下圖二維碼關注,你將感受到一個有趣的靈魂,且每篇文章都有干貨。
          ------------------

          如果覺得文章有點用的話,請毫不留情地素質(zhì)四連吧,分享、點贊、在看、留言,隨你便,這將是我寫作更多優(yōu)質(zhì)文章的最強動力!
          瀏覽 49
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日日夜av无码一二三区 | 麻豆成人91精品二区三区 | 日韩第1页| 无码操逼视频 | 成人做爰黄 片免费观看 |