摘要:是基于鏈接節點的線程安全的隊列。通過這些高效并且線程安全的隊列類,為我們快速搭建高質量的多線程程序帶來極大的便利。隊列內部僅允許容納一個元素。該隊列的頭部是延遲期滿后保存時間最長的元素。
隊列簡述
Queue: 基本上,一個隊列就是一個先入先出(FIFO)的數據結構
Queue接口與List、Set同一級別,都是繼承了Collection接口。LinkedList實現了Deque接 口。
在并發隊列上JDK提供了兩套實現,一個是以ConcurrentLinkedQueue為代表的高性能隊列非阻塞,一個是以BlockingQueue接口為代表的阻塞隊列,無論哪種都繼承自Queue
阻塞隊列與普通隊列的區別在于,當隊列是空的時,從隊列中獲取元素的操作將會被阻塞,或者當隊列是滿時,往隊列里添加元素的操作會被阻塞。試圖從空的阻塞隊列中獲取元素的線程將會被阻塞,直到其他的線程往空的隊列插入新的元素。同樣,試圖往已滿的阻塞隊列中添加新元素的線程同樣也會被阻塞,直到其他的線程使隊列重新變得空閑起來,如從隊列中移除一個或者多個元素,或者完全清空隊列
(圖片來自網絡https://www.cnblogs.com/lemon...)
沒有實現的阻塞接口的LinkedList: 實現了java.util.Queue接口和java.util.AbstractQueue接口
內置的不阻塞隊列: PriorityQueue 和 ConcurrentLinkedQueue
PriorityQueue 和 ConcurrentLinkedQueue 類在 Collection Framework 中加入兩個具體集合實現。
PriorityQueue 類實質上維護了一個有序列表。加入到 Queue 中的元素根據它們的天然排序(通過其 java.util.Comparable 實現)或者根據傳遞給構造函數的 java.util.Comparator 實現來定位。
ConcurrentLinkedQueue 是基于鏈接節點的、線程安全的隊列。并發訪問不需要同步。因為它在隊列的尾部添加元素并從頭部刪除它們,所以只要不需要知道隊列的大小,ConcurrentLinkedQueue 對公共集合的共享訪問就可以工作得很好。收集關于隊列大小的信息會很慢,需要遍歷隊列。
ConcurrentLinkedQueue : 是一個適用于高并發場景下的隊列,通過無鎖的方式,實現
了高并發狀態下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue.它
是一個基于鏈接節點的無界線程安全隊列。該隊列的元素遵循先進先出的原則。頭是最先
加入的,尾是最近加入的,該隊列不允許null元素。
ConcurrentLinkedQueue重要方法:
add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中這倆個方法沒有任何區別)
poll() 和peek() 都是取頭元素節點,區別在于前者會刪除元素,后者不會。
@RequestMapping("test-clq") public void testConcurrentLinkedQueue() { ConcurrentLinkedDequeq = new ConcurrentLinkedDeque<>(); q.offer("Java"); q.offer("C#"); q.offer("Javascript"); q.offer("Python"); // 從頭獲取元素,刪除該元素 System.out.println(q.poll()); // 從頭獲取元素,不刪除該元素 System.out.println(q.peek()); // 獲取總長度 System.out.println(q.size()); // 遍歷 for (String s : q) { System.out.println(s); } }
結果:
Java
C#
3
C#
Javascript
Python
阻塞隊列,顧名思義,首先它是一個隊列,通過一個共享的隊列,可以使得數據由隊列的一端輸入,從另外一端輸出;
常用的隊列主要有以下兩種:(當然通過不同的實現方式,還可以延伸出很多不同類型的隊列,DelayQueue就是其中的一種)
先進先出(FIFO):先插入的隊列的元素也最先出隊列,類似于排隊的功能。從某種程度上來說這種隊列也體現了一種公平性。
后進先出(LIFO):后插入隊列的元素最先出隊列,這種隊列優先處理最近發生的事件。
多線程環境中,通過隊列可以很容易實現數據共享,比如經典的“生產者”和“消費者”模型中,通過隊列可以很便利地實現兩者之間的數據共享。假設我們有若干生產者線程,另外又有若干個消費者線程。如果生產者線程需要把準備好的數據共享給消費者線程,利用隊列的方式來傳遞數據,就可以很方便地解決他們之間的數據共享問題。但如果生產者和消費者在某個時間段內,萬一發生數據處理速度不匹配的情況呢?理想情況下,如果生產者產出數據的速度大于消費者消費的速度,并且當生產出來的數據累積到一定程度的時候,那么生產者必須暫停等待一下(阻塞生產者線程),以便等待消費者線程把累積的數據處理完畢,反之亦然。然而,在concurrent包發布以前,在多線程環境下,我們每個程序員都必須去自己控制這些細節,尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的復雜度。好在此時,強大的concurrent包橫空出世了,而他也給我們帶來了強大的BlockingQueue。(在多線程領域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起的線程又會自動被喚醒)
阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作是:
在隊列為空時,獲取元素的線程會等待隊列變為非空。
當隊列滿時,存儲元素的線程會等待隊列可用。
阻塞隊列常用于生產者和消費者的場景,生產者是往隊列里添加元素的線程,消費者是從隊列里拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器里拿元素。
BlockingQueue即阻塞隊列,從阻塞這個詞可以看出,在某些情況下對阻塞隊列的訪問可能會造成阻塞。被阻塞的情況主要有如下兩種:
當隊列滿了的時候進行入隊列操作
當隊列空了的時候進行出隊列操作
因此,當一個線程試圖對一個已經滿了的隊列進行入隊列操作時,它將會被阻塞,除非有另一個線程做了出隊列操作;同樣,當一個線程試圖對一個空隊列進行出隊列操作時,它將會被阻塞,除非有另一個線程進行了入隊列操作。
在Java中,BlockingQueue的接口位于java.util.concurrent 包中(在Java5版本開始提供),由上面介紹的阻塞隊列的特性可知,阻塞隊列是線程安全的。
在新增的Concurrent包中,BlockingQueue很好的解決了多線程中,如何高效安全“傳輸”數據的問題。通過這些高效并且線程安全的隊列類,為我們快速搭建高質量的多線程程序帶來極大的便利。
下表顯示了jdk1.5中的阻塞隊列的操作:
add 增加一個元索 如果隊列已滿,則拋出一個IIIegaISlabEepeplian異常
remove 移除并返回隊列頭部的元素 如果隊列為空,則拋出一個NoSuchElementException異常
element 返回隊列頭部的元素 如果隊列為空,則拋出一個NoSuchElementException異常
offer 添加一個元素并返回true 如果隊列已滿,則返回false
poll 移除并返問隊列頭部的元素 如果隊列為空,則返回null
peek 返回隊列頭部的元素 如果隊列為空,則返回null
put 添加一個元素 如果隊列滿,則阻塞
take 移除并返回隊列頭部的元素 如果隊列為空,則阻塞
阻塞隊列操作:
aad、remove和element操作在你試圖為一個已滿的隊列增加元素或從空隊列取得元素時 拋出異常
offer、poll、peek方法。這些方法在無法完成任務時,只是給出一個出錯提示而不會拋出異常
阻塞操作put和take。put方法在隊列滿時阻塞,take方法在隊列空時阻塞。
ArrayBlockingQueue是一個有邊界的阻塞隊列,它的內部實現是一個數組。有邊界的意思是它的容量是有限的,我們必須在其初始化的時候指定它的容量大小,容量大小一旦指定就不可改變。
ArrayBlockingQueue是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部
LinkedBlockingQueue阻塞隊列大小的配置是可選的,如果我們初始化時指定一個大小,它就是有邊界的,如果不指定,它就是無邊界的。說是無邊界,其實是采用了默認大小為Integer.MAX_VALUE的容量 。它的內部實現是一個鏈表。
和ArrayBlockingQueue一樣,LinkedBlockingQueue 也是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。
PriorityBlockingQueue是一個沒有邊界的隊列,它的排序規則和 java.util.PriorityQueue一樣。需要注意,PriorityBlockingQueue中允許插入null對象。
所有插入PriorityBlockingQueue的對象必須實現 java.lang.Comparable接口,隊列優先級的排序規則就是按照我們對這個接口的實現來定義的。
另外,我們可以從PriorityBlockingQueue獲得一個迭代器Iterator,但這個迭代器并不保證按照優先級順序進行迭代。
SynchronousQueue隊列內部僅允許容納一個元素。當一個線程插入一個元素后會被阻塞,除非這個元素被另一個線程消費
DelayQueue(基于PriorityQueue來實現的)是一個存放Delayed 元素的無界阻塞隊列,只有在延遲期滿時才能從中提取元素。該隊列的頭部是延遲期滿后保存時間最長的 Delayed 元素。如果延遲都還沒有期滿,則隊列沒有頭部,并且poll將返回null。當一個元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一個小于或等于零的值時,則出現期滿,poll就以移除這個元素了。此隊列不允許使用 null 元素。
使用BlockingQueue模擬生產者與消費者生產者
public class ProducerThread implements Runnable { private BlockingQueuequeue; private AtomicInteger count = new AtomicInteger(); private volatile boolean FLAG = true; public ProducerThread(BlockingQueue queue) { this.queue = queue; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "生產者開始啟動...."); while (FLAG) { String data = count.incrementAndGet() + ""; try { boolean offer = queue.offer(data, 2, TimeUnit.SECONDS); if (offer) { System.out.println(Thread.currentThread().getName() + ",生產隊列" + data + "成功.."); } else { System.out.println(Thread.currentThread().getName() + ",生產隊列" + data + "失敗.."); } Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ",生產者線程停止..."); } public void stop() { this.FLAG = false; } }
消費者
public class ConsumerThread implements Runnable { private volatile boolean FLAG = true; private BlockingQueueblockingQueue; public ConsumerThread(BlockingQueue blockingQueue) { this.blockingQueue = blockingQueue; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "消費者開始啟動...."); while (FLAG) { try { String data = blockingQueue.poll(2, TimeUnit.SECONDS); if (data == null || data == "") { FLAG = false; System.out.println("消費者超過2秒時間未獲取到消息."); return; } System.out.println("消費者獲取到隊列信息成功,data:" + data); } catch (Exception e) { e.printStackTrace(); } } } }
請求
@RequestMapping("test-blockingQueue") public void testBlockingQueue() { LinkedBlockingDequeblockingDeque = new LinkedBlockingDeque<>(1); ProducerThread producerThread = new ProducerThread(blockingDeque); ConsumerThread consumerThread = new ConsumerThread(blockingDeque); Thread t1 = new Thread(producerThread, "生產者"); Thread t2 = new Thread(consumerThread, "消費者"); t1.start(); t2.start(); // 10秒后停止線程 try { Thread.sleep(10 * 1000); producerThread.stop(); } catch (InterruptedException e) { e.printStackTrace(); } }
結果
消費者消費者開始啟動....
生產者生產者開始啟動....
生產者,生產隊列1成功..
消費者獲取到隊列信息成功,data:1
生產者,生產隊列2成功..
消費者獲取到隊列信息成功,data:2
生產者,生產隊列3成功..
消費者獲取到隊列信息成功,data:3
生產者,生產隊列4成功..
消費者獲取到隊列信息成功,data:4
生產者,生產隊列5成功..
消費者獲取到隊列信息成功,data:5
生產者,生產隊列6成功..
消費者獲取到隊列信息成功,data:6
生產者,生產隊列7成功..
消費者獲取到隊列信息成功,data:7
生產者,生產隊列8成功..
消費者獲取到隊列信息成功,data:8
生產者,生產隊列9成功..
消費者獲取到隊列信息成功,data:9
生產者,生產隊列10成功..
消費者獲取到隊列信息成功,data:10
生產者,生產者線程停止...
消費者超過2秒時間未獲取到消息.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74729.html
摘要:知識點總結容器知識點總結容器接口與是在同一級別,都是繼承了接口。另一種隊列則是雙端隊列,支持在頭尾兩端插入和移除元素,主要包括。一個由鏈表結構組成的無界阻塞隊列。是一個阻塞的線程安全的隊列,底層實現也是使用鏈式結構。 Java知識點總結(Java容器-Queue) @(Java知識點總結)[Java, Java容器] Queue Queue接口與List、Set是在同一級別,都是繼承了...
摘要:語言在之前,提供的唯一的并發原語就是管程,而且之后提供的并發包,也是以管程技術為基礎的。但是管程更容易使用,所以選擇了管程。線程進入條件變量的等待隊列后,是允許其他線程進入管程的。并發編程里兩大核心問題互斥和同步,都可以由管程來幫你解決。 并發編程這個技術領域已經發展了半個世紀了。有沒有一種核心技術可以很方便地解決我們的并發問題呢?這個問題, 我會選擇 Monitor(管程)技術。Ja...
摘要:什么是阻塞隊列阻塞隊列是一個在隊列基礎上又支持了兩個附加操作的隊列。阻塞隊列的應用場景阻塞隊列常用于生產者和消費者的場景,生產者是向隊列里添加元素的線程,消費者是從隊列里取元素的線程。由鏈表結構組成的無界阻塞隊列。 什么是阻塞隊列? 阻塞隊列是一個在隊列基礎上又支持了兩個附加操作的隊列。 2個附加操作: 支持阻塞的插入方法:隊列滿時,隊列會阻塞插入元素的線程,直到隊列不滿。 支持阻塞的...
閱讀 1463·2021-11-24 09:39
閱讀 1781·2021-11-22 15:25
閱讀 3732·2021-11-19 09:40
閱讀 3291·2021-09-22 15:31
閱讀 1293·2021-07-29 13:49
閱讀 1201·2019-08-26 11:59
閱讀 1313·2019-08-26 11:39
閱讀 927·2019-08-26 11:00