8.併發容器ConcurrentHashMap#put方法解析

来源:http://www.cnblogs.com/yulinfeng/archive/2017/06/07/6959374.html
-Advertisement-
Play Games

jdk1.7.0_79 HashMap可以說是每個Java程式員用的最多的數據結構之一了,無處不見它的身影。關於HashMap,通常也能說出它不是線程安全的。這篇文章要提到的是在多線程併發環境下的HashMap——ConcurrentHashMap,顯然它必然是線程安全的,同樣我們不可避免的要討論散 ...


jdk1.7.0_79

  HashMap可以說是每個Java程式員用的最多的數據結構之一了,無處不見它的身影。關於HashMap,通常也能說出它不是線程安全的。這篇文章要提到的是在多線程併發環境下的HashMap——ConcurrentHashMap,顯然它必然是線程安全的,同樣我們不可避免的要討論散列表,以及它是如何實現線程安全的,它的效率又是怎樣的,因為對於映射容器還有一個Hashtable也是線程安全的但它似乎只出現在筆試、面試題里,在現實編碼中它已經基本被遺棄。

  關於HashMap的線程不安全,在多線程併發環境下它所帶來的影響絕不僅僅是出現臟數據等數據不一致的情況,嚴重的是它有可能帶來程式死迴圈,這可能有點不可思議,但確實在不久前的項目里同事有遇到了CPU100%滿負荷運行,分析結果是在多線程環境下HashMap導致程式死迴圈。對於Hashtable,查看其源碼可知,Hashtable保證線程安全的方式就是利用synchronized關鍵字,這樣會導致效率低下,但對於ConcurrentHashMap則採用了不同的線程安全保證方式——分段鎖。它不像Hashtable那樣將整個table鎖住而是將數組元素分段加鎖,如果線程1訪問的元素在分段segment1,而線程2訪問的元素在分段segment2,則它們互不影響可以同時進行操作。如果合理的進行分段就是其關鍵問題。

  ConcurrentHashMap和HashMap的結果基本一致,同樣也是Entry作為存放數據的對象,另外一個就是上面提到的分段鎖——Segment。它繼承自ReentrantLock(關於ReentrantLock,可參考《5.Lock介面及其實現ReentrantLock》),故它具有ReentrantLock一切特性——可重入,獨占等。

  ConcurrentHashMap的結構圖如下所示:

  可以看到相比較於HashMapConcurrentHashMap在Entry數組之上是Segment,這個就是我們上面提到的分段鎖,合理的確定分段數就能更好的提高併發效率,我們來看ConcurrentHashMap是如何確定分段數的。 

  ConcurrentHashMap的初始化時通過其構造函數public ConcurrentHashMap(int initialCapacity, float loadFactorint concurrencyLevel)完成的,若在不指定各參數的情況下,初始容量initialCapacity=DAFAULT_INITIAL_CAPACITY=16,負載因數loadFactor=DEFAULT_LOAD_FACTOR=0.75f,併發等級concurrencyLevel=DEFAULT_CONCURRENCY_LEVEL=16,前兩者和HashMap相同。至於負載因數表示一個散列表的空間的使用程度initialCapacity(總容量) * loadFactor(負載因數) = 數據量,有此公式可知,若負載因數越大,則散列表的裝填程度越高,也就是能容納更多的元素,但這樣元素就多,鏈表就大,此時索引效率就會降低。若負載因數越小,則相反,索引效率就會高,換來的代價就是浪費的空間越多併發等級它表示估計最多有多少個線程來共同修改這個Map,稍後可以看到它和segment數組相關,segment數組的長度就是通過concurrencyLevel計算得出。

 1 //以預設參數為例initalCapacity=16,loadFactor=0.75,concurrencyLevel=16 
 2 public ConcurrentHashMap(int initalCapacity, float loadFactor, int concurrencyLevel) { 
 3   if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) 
 4     throw new IllegalArgumentException(); 
 5   if (concurrencyLevel > MAX_SEGMENTS) 
 6     concurrencyLevel = MAX_SEGMENTS; 
 7   int sshift = 0; 
 8   int ssize = 1;//segment數組長度 
 9   while (ssize < concurrencyLevel) { 
10     ++sshift; 
11     ssize <= 1; 
12   }//經過ssize左移4位後,ssize=16,ssift=4 
13 /*segmentShift用於參與散列運算的位數,segmentMask是散列運算的掩碼,這裡有關的散列函數運算和HashMap有類似之處*/ 
14   this.segmentShift = 32 – ssift;//段偏移量segmentShift=28 
15   this.segmentMask = ssize – 1;//段掩碼segmentMask=15(1111) 
16   if (initialCapacity > MAXIMUM_CAPACITY) 
17     initialCapacity = MAXIMUM_CAPACITY; 
18   int c = initialCapacity / ssize;//c = 1 
19   if (c * ssize < initialCapacity) 
20     ++c; 
21   int cap = MIN_SEGMENT_TABLE_CAPACITY;//MIN_SEGMENT_TABLE_CAPACITY=2 
22   while (cap < c)//cap = 2, c = 1,false 
23       cap <<= 1;//cap是segment里HashEntry數組的長度,最小為2 
24 /*創建segments數組和segment[0]*/ 
25   Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[]) new HashEntry[cap]);//參數意為:負載因數=1,數據容量=(int)(2 * 0.75)=1,總容量=2,故每個Segment的HashEntry總容量為2,實際數據容量為1 
26   Segment<K,V> ss = (Segment<K,V>[])new Segment[ssize];//segments數組大小為16 
27   UNSAFE.putOrderedObject(ss, SBASE, s0); 
28   this.segments = ss; 
29 } 

  以上就是整個初始化過程,主要是初始化segments的長度大小以及通過負載因數確定每個Segment的容量大小。確定好Segment過後,接下來的重點就是如何準確定位Segment。定位Segment的方法就是通過散列函數來定位,先通過hash方法對元素進行二次散列,這個演算法較為複雜,其目的只有一個——減少散列衝突,使元素能均勻分佈在不同的Segment上,提高容器的存取效率。 

  我們通過最直觀最常用的put方法來觀察ConcurrentHashMap是如何通過key值計算hash值在定位到Segment的 

 1 //ConcurrentHashMap#put 
 2 public V put(K key, V value) { 
 3   Segment<K,V> s; 
 4   if (value == null) 
 5     throw new NullPointerException(); 
 6   int hash = hash(key);//根據散列函數,計算出key值的散列值 
 7   int j = (hash >>> segmentShift) & segmentMask;//這個操作就是定位Segment的數組下標,jdk1.7之前是segmentFor返回Segment,1.7之後直接就取消了這個方法,直接計算數組下標,然後通過偏移量底層操作獲取Segment 
 8   if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck 
 9       (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment 
10     s = ensureSegment(j);//通過便宜量定位不到就調用ensureSegment方法定位Segment 
11   return s.put(key, hash, value, false); 
12 } 

  Segment.put方法就是將鍵、值構造為Entry節點加入到對應的Segment段里,如果段中已經有元素(即表示兩個key鍵值的hash值重覆)則將最新加入的放到鏈表的頭),整個過程必然是加鎖安全的。 

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作業:購物商城 商品展示,價格 買,加入購物車 付款,錢不夠 流程圖如下: 代碼共有4個文件,如下: 用戶文件: 商品文件: 購物車文件: 錢包文件: 代碼如下: 上述代碼運行流程如下: (1)展示商品信息; (2)用戶登錄驗證; (3)用戶輸入想購買產品及數量,輸入quit退出購物; (4)添加到 ...
  • 實際項目中pom.xml依賴寫法: Maven 安裝 JAR 包的命令是: 例如我的這個spring-context-support-3.1.0.RELEASE.jar 文件放在了"D:\mvn\"中 則命令為(在cmd中執行): mvn install:install-file -Dfile=D: ...
  • 基於51單片機的萬年曆,用到了單片機獨立鍵盤、數位管、LED燈模塊實現。 想要簡單還是DS1302好用。 -- -- -- 參考: http://www.cnblogs.com/LXSYD-C/p/6364888.html 如有錯誤還請指出,如有侵權還請告知,如需轉載請註明出處! 本人博客:http ...
  • 給人搬了十幾個網站,老站用西部數位建站助手創建的,現在過期了無法繼續創建,只能在Internet 信息服務(IIS)管理器創建網站,創建下來都沒問題,但是就是無法打開網站。 測試打開txt文檔、靜態頁面都能打開,一到打開php文件就直接就掛了,無法打開,什麼報錯都沒有。 之前有用iis6以外的伺服器 ...
  • 嘗試用springmvc,mybatis,mysql做個工具平臺。 在本地mac筆記本上運行正常,但把包放置到伺服器上,啟動tomcat就報錯。類找不到了。 文件目錄: 實現需求:上傳文檔並記錄在資料庫中。自建了DocFile類。創建對應的mapper文件寫sql語句。 mapper.xml中nam ...
  • 通常我們的做法是(尤其是在學習階段):定義一個新的變數,藉助它完成交換。代碼如下: int a,b; a=10; b=15; int t; t=a; a=b; b=t; 這種演算法易於理解,特別適合幫助初學者瞭解電腦程式的特點,是賦值語句的經典應用。在實際軟體開發當中,此演算法簡單明瞭,不會產生歧義, ...
  • 一、編程規約 (一) 命名規約 1. 【強制】 代碼中的命名均不能以下劃線或美元符號開始,也不能以下劃線或美元符號結束。 反例: _nam / __name / $Object / name_ / name$ / Object$2. 【強制】 代碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使 ...
  • 網上有很多人探討Java中異常捕獲機制try...catch...finally塊中的finally語句是不是一定會被執行?很多人都說不是,當然他們的回答是正確的,經過我試驗,至少有兩種情況下finally語句是不會被執行的: (1)try語句沒有被執行到,如在try語句之前就返回了,這樣final ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...