Java最佳實踐–隊列之戰和鏈接的ConcurrentHashMap

在使用Java編程語言時,我們將繼續討論與建議的實踐有關的系列文章,我們將在四個具有相關語義的流行Queue實現類之間進行性能比較。 為了使事情變得更現實,我們將在多線程環境下進行測試,以討論和演示如何將ArrayBlockingQueue , ConcurrentLinkedQueue , LinkedBlockingQueue和/或LinkedList用于高性能應用程序。

最后但并非最不重要的一點是,我們將提供自己的ConcurrentHashMap實現。 從ConcurrentLinkedHashMap實施不同的ConcurrentHashMap ,它維持于所有條目的運行雙向鏈表。 此鏈表定義了迭代順序,通常是將鍵插入映射中的順序。 從組合的特點ConcurrentLinkedHashMap利益的ConcurrentHashMap和LinkedHashMap的實現業績略低于該的ConcurrentHashMap的 ,由于增加了維護鏈接列表的額外費用。

所有討論的主題均基于用例,這些用例來自于電信行業的關鍵任務超高性能生產系統的開發。

在閱讀本文的每個部分之前,強烈建議您參考相關的Java API文檔以獲取詳細信息和代碼示例。

所有測試均針對具有以下特征的Sony Vaio進行:

  • 系統:openSUSE 11.1(x86_64)
  • 處理器(CPU):Intel(R)Core(TM)2 Duo CPU T6670 @ 2.20GHz
  • 處理器速度:1,200.00 MHz
  • 總內存(RAM):2.8 GB
  • Java:OpenJDK 1.6.0_0 64位

應用以下測試配置:

  • 并發工作線程數:50
  • 每個工作人員測試重復次數:100
  • 整體測試次數:100

ArrayBlockingQueue與ConcurrentLinkedQueue與LinkedBlockingQueue與LinkedList

Java開發人員必須執行的最常見任務之一是從Collections中存儲和檢索對象。 Java編程語言提供了一些具有重疊和獨特特征的Collection實現類。 從Java 1.5開始, Queue實現類已成為在處理之前保存元素的事實上的標準。 除了基本的“ 收集”操作外,隊列還提供其他插入,提取和檢查操作。

但是,使用Queue實現類,尤其是在多線程環境中,可能會很棘手。 默認情況下,它們中的大多數提供并發訪問,但是可以以阻塞或非阻塞方式來處理并發。 BlockingQueue實現類支持以下操作:在檢索元素時等待隊列變為非空,并在存儲元素時等待隊列中的空間變為可用。

這里將要討論的案例場景是通過ConcurrentLinkedQueue和LinkedList Queue實現類以及ArrayBlockingQueue和LinkedBlockingQueue BlockingQueue實現類的元素來插入,縮回和迭代多個線程 。 我們將演示如何在多線程環境中正確利用上述集合實現類,并提供相關的性能比較表,以顯示在每個測試用例中哪個性能更好。

為了進行票價比較,我們將假定不允許使用NULL元素,并且在適用的情況下限制每個隊列的大小。 因此,我們的測試組的BlockingQueue實現類將被初始化,其最大大小為5000個元素–請記住,我們將使用50個工作線程來執行100次測試重復。 此外,由于LinkedList是我們測試組中唯一不默認提供并發訪問的Queue實現類,因此將使用同步塊訪問列表來實現LinkedList的并發性。 在這一點上,我們必須明確指出LinkedList Collection實現類的Java文檔建議使用Collections.synchronizedList靜態方法來維護對列表的并發訪問。 此方法提供指定Collection實現類的“包裝”同步實例,如下所示:

列表syncList = Collections.synchronizedList(new LinkedList());

當您要將特定的實現類用作List而不是Queue時,此方法是合適的。 為了能夠使用特定Collection實現類的“隊列”功能,必須按原樣使用它,或將其強制轉換為Queue接口。

測試案例1 –在隊列中添加元素

對于第一個測試用例,我們將有多個線程在每個Queue實現類中添加String元素。 為了保持String元素之間的唯一性,我們將如下所示構造它們:

  • 靜態的第一部分,例如“ helloWorld”
  • 工作線程ID,請記住,我們有50個并發運行的工作線程
  • worker線程測試重復次數,請記住,每個worker線程每次測試執行100次測試重復

對于每個測試運行,每個工作線程將插入100個String元素,如下所示:

  • 對于第一次測試重復
    • 工作線程1將插入String元素:“ helloWorld-1-1”
    • 工作線程2將插入String元素:“ helloWorld-2-1”
    • 工作線程3將插入String元素:“ helloWorld-3-1”
    • 等等...
  • 對于第二次測試重復
    • 工作線程1將插入String元素:“ helloWorld-1-2”
    • 工作線程2將插入String元素:“ helloWorld-2-2”
    • 工作線程3將插入String元素:“ helloWorld-3-2”
    • 等等...
  • 等等...

在每次測試運行結束時,每個Queue實現類都將填充5000個不同的String元素。 為了添加元素,我們將使用BlockingQueue實現類的“ put()”操作和Queue實現類的“ offer()”操作,如下所示:

  • arrayBlockingQueue.put(“ helloWorld-” + id +“-” + count);
  • linkedBlockingQueue.put(“ helloWorld-” + id +“-” + count);
  • parallelLinkedQueue.offer(“ helloWorld-” + id +“-” + count);
  • 已同步(linkedList){
    linkedList.offer(“ helloWorld-” + id +“-” + count);
    }

下面我們提供了上述四個Queue實現類之間的性能比較表

橫軸表示測試運行的次數,縱軸表示每次測試運行的每秒平均事務數(TPS)。 因此,較高的值更好。 如您所見,向其添加元素時,所有Queue實現類的執行幾乎相同。 與LinkedList和LinkedBlockingQueue相比, ArrayBlockingQueue和ConcurrentLinkedQueue的性能稍好。 后者表現最差,平均得分為625000 TPS。

測試案例2 –從隊列中刪除元素

對于第二個測試用例,我們將有多個線程從每個Queue實現類中刪除String元素。 所有隊列實現類都將使用來自先前測試用例的String元素進行預填充。 每個線程將從每個Queue實現類中刪除單個元素,直到Queue為空。

為了刪除元素,我們將對BlockingQueue實現類使用“ take()”操作,對Queue實現類使用“ poll()”操作,如下所示:

  • arrayBlockingQueue.take();
  • linkedBlockingQueue.take();
  • concurrentLinkedQueue.poll();
  • 已同步(linkedList){
    linkedList.poll();
    }

下面我們提供了上述四個Queue實現類之間的性能比較表

橫軸表示測試運行的次數,縱軸表示每次測試運行的每秒平均事務數(TPS)。 因此,較高的值更好。 同樣,從隊列中刪除String元素時,所有Queue實現類的性能幾乎相同。 與LinkedList和LinkedBlockingQueue相比, ArrayBlockingQueue和ConcurrentLinkedQueue的性能稍好。 后者是最差的性能,平均得分為710000 TPS。

測試案例#3 –迭代器

對于第三個測試用例,我們將有多個工作線程在每個Queue實現類的元素上進行迭代。 每個工作線程將使用Queue的“ iterator()”操作檢索對Iterator實例的引用,并使用Iterator的“ next()”操作遍歷所有可用的Queue元素。 所有Queue實現類都將使用第一個測試用例的String值預先填充。 下面是上述測試用例的性能比較表。

橫軸表示測試運行的次數,縱軸表示每次測試運行的每秒平均事務數(TPS)。 因此,較高的值更好。 與ConcurrentLinkedQueue和LinkedList實現類相比, ArrayBlockingQueue和LinkedBlockingQueue實現類的性能均較差。 LinkedBlockingQueue平均得分為35 TPS,而ArrayBlockingQueue平均得分為81 TPS。 另一方面, LinkedList的性能優于ConcurrentLinkedQueue ,平均導致15000 TPS。

測試案例4 –添加和刪除元素

對于我們的最終測試用例,我們將實現測試用例1和測試用例2場景的組合。 換句話說,我們將實現生產者-消費者測試用例。 一組輔助線程將向每個Queue實現類插入String元素,而另一組輔助線程將從其中撤回String元素。 每一個主題從“插入元素”組將會只有一個元素插入,而每一次的主題從“縮回元素”組是要收回一個元素。 因此,我們將從每個Queue實現類中同時插入和撤回5000個唯一的String元素。

要正確模擬上述測試情況下,我們必須啟動所有工作線程是收回之前的元素開始工作者線程是插入的元素。 為了使“收回元素”組的工作線程能夠收回單個元素,如果相關隊列為空,它們必須等待并重試。 默認情況下, BlockingQueue實現類提供等待功能,而Queue實現類則不提供。 因此,為了刪除元素,我們將對BlockingQueue實現類使用“ take()”操作,對Queue實現類使用“ poll()”操作,如下所示:

  • arrayBlockingQueue.take();
  • linkedBlockingQueue.take();
  • while(結果== null)
    結果= parallelLinkedQueue.poll();
  • while(結果== null)
    已同步(linkedList){
    結果= linkedList.poll(); }

    如您所見,我們實現了最低限度的while循環,以使ConcurrentLinkedQueue和LinkedList使用者能夠在從空Queue撤回時執行重新連接。 當然,您可以嘗試并實施更復雜的方法。 盡管如此,請記住,上述內容以及任何其他人工實現方法都不是建議的解決方案,應避免使用BlockingQueue “ take()”操作,如以下性能比較表所示。

    下面是上述測試用例的添加部分的性能比較表。

    以下是上述測試案例的收回部分的性能比較表。

    橫軸表示測試運行的次數,縱軸表示每次測試運行的每秒平均事務數(TPS)。 因此,較高的值更好。 不出所料, ArrayBlockingQueue和LinkedBlockingQueue的表現均優于LinkedList和ConcurrentLinkedQueue 。 BlockingQueue實現被設計為主要用于生產者-消費者隊列。 在這種情況下,特別是當生產者線程和使用者線程的數量相對較高時,它們的阻塞行為使它們具有無與倫比的性能和效率。 實際上,根據相關的性能比較,隨著生產者線程和使用者線程數量的增加, Queue實現類和BlockingQueue實現類之間的性能相對增益會有所提高,而后者則有所提高。

    從提供的性能結果中可以看出, LinkedBlockingQueue獲得了最佳的組合(添加和刪除元素)性能結果,并且應該是實施生產者(消費者)方案的第一候選人。

    ConcurrentLinkedHashMap

    我們的ConcurrentLinkedHashMap實現是最初由Doug Lea編碼并在OpenJDK 1.6.0_0上發現的ConcurrentHashMap實現的調整版本。 我們提出了ConcurrentMap接口的并發哈希圖和鏈表實現,并具有可預測的迭代順序。 此實現與ConcurrentHashMap的不同之處在于,它維護一個遍歷其所有條目的雙向鏈接列表。 此鏈表定義了迭代順序,通常是將鍵插入映射的順序(插入順序)。 請注意,如果將密鑰重新插入到映射中,則插入順序不會受到影響。

    此實現使客戶擺脫了ConcurrentHashMap和Hashtable提供的未指定的,通常混亂的排序,而不會增加與TreeMap相關的成本。 無論原始地圖的實現如何,都可以使用它來生成與原始地圖具有相同順序的地圖副本:

    void foo(Map m){
    地圖副本=新的ConcurrentLinkedHashMap(m);
    … }

    如果模塊在輸入上獲取映射,將其復制并隨后返回結果(其順序由副本的順序確定),則此技術特別有用。 (客戶通常喜歡按退貨的順序退貨。)

    提供了一個特殊的“ ConcurrentLinkedHashMap(int,float,int,boolean)”構造函數,以創建并發鏈接的哈希映射,該哈希映射的迭代順序是其條目的最后訪問順序,即從最近訪問到最近訪問(訪問-訂購)。 這種映射非常適合構建LRU緩存。 調用put或get方法將導致對相應條目的訪問(假設調用完成后該條目存在)。 “ putAll()”方法為指定映射中的每個映射生成一個條目訪問,其順序為指定映射的條目集迭代器提供鍵-值映射。 沒有其他方法可以生成條目訪問。 特別是,對集合視圖的操作不會影響支持映射的迭代順序。

    可以重寫“ removeEldestEntry(Map.Entry)”方法,以強加一個策略,以便在將新映射添加到地圖時自動刪除陳舊的映射。

    由于維護鏈接列表會增加開銷,因此性能可能會略低于ComcurrentHashMap,但有一個例外:對ConcurrentLinkedHashMap的集合視圖進行迭代需要的時間與地圖的大小成正比,而無論其容量如何。 在ConcurrentHashMap上進行迭代可能會更昂貴,所需的時間與其容量成正比。

    您可以從此處下載最新版本的ConcurrentLinkedHashMap源代碼和二進制代碼

    您可以從此處 , 此處和此處下載用于進行性能比較的所有“測試器”類的源代碼。

    快樂編碼

    賈斯汀

    相關文章 :

    • Java最佳實踐–多線程環境中的DateFormat
    • Java最佳實踐–高性能序列化
    • Java最佳實踐– Vector vs ArrayList vs HashSet
    • Java最佳實踐–字符串性能和精確字符串匹配
    • Java最佳實踐– Char到Byte和Byte到Char的轉換

    翻譯自: https://www.javacodegeeks.com/2010/09/java-best-practices-queue-battle-and.html

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/news/374838.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/news/374838.shtml
    英文地址,請注明出處:http://en.pswp.cn/news/374838.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    HDU 5652 India and China Origins(二分 + BFS)

    本文鏈接:http://www.cnblogs.com/Ash-ly/p/5398867.html 題意: 中國和印度之間有一片地方,把這片地方抽象化,于是就可以看成一個N * M矩陣,其中黑色的代表高山不能走過去,白色的代表平原,可以通行,人每次可…

    C語言%.2f四舍五入

    #include <stdio.h> int main() {double d 1.199;printf("%.2f", d);return 0; }輸出1.20 如果不想讓其四舍五入可以這樣&#xff1a; #include <stdio.h> #include <math.h> int main() {double d 1.199;printf("%.2f", floor(d * 1…

    關于使用racthet的push.js

    racthet的push是用來跳轉另外一個頁面的效果的。但是必須在服務器的環境下支持。如果想要讓本地html訪問支持的話需要添加 轉載于:https://www.cnblogs.com/djawh/p/4623925.html

    休眠自動提交命令強制MySQL在過多的磁盤I / O中運行

    親愛的大家&#xff0c; 我敢肯定&#xff0c;你們中的許多人都在使用Hibernate和MySQL&#xff0c;我自己在這里和那里都使用它。 通常&#xff0c;編程模型是不錯的&#xff0c;但是普通的JDBC可以快很多已經不是什么秘密了。 在這篇文章中&#xff0c;我想引起您的注意Hibe…

    “應用程序無法正常啟動(oxc000007b)”解決方案

    解決方案1 通過“DirectX修復工具 V3.3 標準版”軟件修復。 備注&#xff1a;經過測試&#xff0c;并未解決本人的問題&#xff0c;但是這個方法可能對游戲中缺失相關.dll&#xff08;動態鏈接庫&#xff09;有幫助。 解決方案2&#xff1a; 該問題的出現不適偶然&#xff0c;主…

    Linux: dev: cmake: CHECK_LIBRARY_EXISTS

    文章目錄 簡介例子源代碼最終調用到的兩個命令如果結果是這里為什么不直接使用rpm查看包呢&#xff1f;需要注意的問題 簡介 https://cmake.org/cmake/help/latest/module/CheckLibraryExists.html 這個方法是在Modules/CheckLibraryExists.cmake文件里定義的一個宏。 最終使用…

    7-15 計算圓周率 (15 分)

    根據下面關系式&#xff0c;求圓周率的值&#xff0c;直到最后一項的值小于給定閾值。 輸入格式&#xff1a; 輸入在一行中給出小于1的閾值。 輸出格式&#xff1a; 在一行中輸出滿足閾值條件的近似圓周率&#xff0c;輸出到小數點后6位。 輸入樣例&#xff1a; 0.01結尾無…

    Struts2的全局結果視圖的配置

    1.在struts.xml中的package標簽內添加<global-results/>標簽&#xff0c;將全局結果加進該標簽內&#xff0c;只適用于當前包下。 <package name"customer" namespace"/customer" extends"struts-default" > <global-results>…

    長大了Java! 提出Java桌面版

    不&#xff0c;這不是另一個“ Java已死”的咆哮。 Java非常活躍。 它是可用的最佳開發和運行時平臺之一。 迄今為止&#xff0c;最穩定的平臺。 那可能只是它最大的禍根。 荒謬&#xff01; 穩定性如何&#xff1f; 你可能會問。 好吧&#xff0c;由于它&#xff0c;您可以看到…

    [算法練習]Excel Sheet Column Title

    題目&#xff1a; Given a positive integer, return its corresponding column title as appear in an Excel sheet. For example: 1 -> A 2 -> B 3 -> C ... 26 -> Z 27 -> AA 28 -> AB 代碼&#xff1a; class Solution { public: string convertToTitle(…

    7-16 求符合給定條件的整數集 (15 分)

    給定不超過6的正整數A&#xff0c;考慮從A開始的連續4個數字。請輸出所有由它們組成的無重復數字的3位數。 輸入格式&#xff1a; 輸入在一行中給出A。 輸出格式&#xff1a; 輸出滿足條件的的3位數&#xff0c;要求從小到大&#xff0c;每行6個整數。整數間以空格分隔&#…

    JUnit學習之hamcrest、testSuite介紹及測試原則

    [轉自] http://huihai.iteye.com/blog/1994270 上一節說了junit的一些基本概念&#xff0c;主要使用assert做一些基本的判斷。但很多時候使用assert做判斷&#xff0c;并不方便&#xff0c;如果要判斷某幾個值是否為true或false&#xff0c;這時使用hamcrest來判斷就會方便許多…

    Java最佳實踐– Vector vs ArrayList vs HashSet

    在使用Java編程語言時&#xff0c;我們將繼續討論與建議的實踐有關的系列文章&#xff0c;我們將在三個最常用的Collection實現類之間進行性能比較。 為了使事情變得更現實&#xff0c;我們將在多線程環境下進行測試&#xff0c;以討論和演示如何將Vector &#xff0c; ArrayLi…

    iOS:圖片上傳時兩種圖片壓縮方式的比較

    上傳圖片不全面的想法&#xff1a;把圖片保存到本地,然后把圖片的路徑上傳到服務器&#xff0c;最后又由服務器把路徑返回&#xff0c;這種方式不具有擴展性&#xff0c;如果用戶換了手機&#xff0c;那么新手機的沙盒中就沒有服務器返回的圖片路徑了&#xff0c;此時就無法獲取…

    7-17 爬動的蠕蟲 (15 分)

    一條蠕蟲長1寸&#xff0c;在一口深為N寸的井的底部。已知蠕蟲每1分鐘可以向上爬U寸&#xff0c;但必須休息1分鐘才能接著往上爬。在休息的過程中&#xff0c;蠕蟲又下滑了D寸。就這樣&#xff0c;上爬和下滑重復進行。請問&#xff0c;蠕蟲需要多長時間才能爬出井&#xff1f;…

    淺談泛型之泛型方法

    實際不用多說只舉2個例子就行: //例1 static void fromArrayToCollection(Object[] a, Collection<?> c) {for (Object o : a) { c.add(o); // 編譯錯誤,錯誤原因也很簡單,<?>是無上下界的通配符泛型,所以編譯器根本無法確認類型} } //例2 static <T> void…

    Android特效 五種Toast詳解

    Toast是Android中用來顯示顯示信息的一種機制&#xff0c;和Dialog不一樣的是&#xff0c;Toast是沒有焦點的&#xff0c;而且Toast顯示的時間有限&#xff0c;過一定的時間就會自動消失。而且Toast主要用于向用戶顯示提示消息&#xff0c;接下來巴士為大家總結了Android五種To…

    Java最佳實踐–高性能序列化

    在使用Java編程語言時&#xff0c;我們將繼續討論與建議的實踐有關的系列文章&#xff0c;我們將討論并演示如何將對象序列化用于高性能應用程序。 所有討論的主題均基于用例&#xff0c;這些用例來自于電信行業的關鍵任務超高性能生產系統的開發。 在閱讀本文的每個部分之前…

    7-18 二分法求多項式單根 (20 分)

    二分法求函數根的原理為&#xff1a;如果連續函數f(x)在區間[a,b]的兩個端點取值異號&#xff0c;即f(a)f(b)<0&#xff0c;則它在這個區間內至少存在1個根r&#xff0c;即f0。 二分法的步驟為&#xff1a; 檢查區間長度&#xff0c;如果小于給定閾值&#xff0c;則停止&a…

    java只使用try和finally不使用catch的原因和場景

    JDK并發工具包中&#xff0c;很多異常處理都使用了如下的結構&#xff0c;如AbstractExecutorService&#xff0c;即只有try和finally沒有catch。 class X {private final ReentrantLock lock new ReentrantLock();// ...public void m(){lock.lock(); // block until condi…