JDK1.7 ConcurrentHashMap 源碼淺析

来源:http://www.cnblogs.com/xiongpq/archive/2016/12/15/6185046.html
-Advertisement-
Play Games

概述 ConcurrentHashMap是HashMap的線程安全版本,使用了分段加鎖的方案,在高併發時有比較好的性能。 本文分析JDK1.7中ConcurrentHashMap的實現。 正文 ConcurrentHashMap概述 HashMap不是線程安全的,要實現線程安全除非加鎖,但這樣性能很 ...


概述

ConcurrentHashMap是HashMap的線程安全版本,使用了分段加鎖的方案,在高併發時有比較好的性能。

本文分析JDK1.7中ConcurrentHashMap的實現。

正文

ConcurrentHashMap概述

HashMap不是線程安全的,要實現線程安全除非加鎖,但這樣性能很低。ConcurrentHashMap把整個HashMap數組分成了若幹個Segment,每個Segment里有一個數組。添加一個Key時,需要先根據hash值計算出其所在Segment,然後再根據hash值計算出在該Segment中的位置。Segment繼承自ReentrantLock,每個Segment就是一個鎖。在多線程的情況下,就減少了鎖競爭,提升了性能。

ConcurrentHashMap存儲結構如下圖所示:

image

下麵我們來分析源碼,看數據是怎麼存儲的。

構造函數

public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
    if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    if (concurrencyLevel > MAX_SEGMENTS)
        concurrencyLevel = MAX_SEGMENTS;
    // Find power-of-two sizes best matching arguments
    int sshift = 0;
    int ssize = 1;
    //concurrencyLevel為併發級別,這一步就是計算出大於concurrencyLevel的最小的2的N次方
    //為什麼不用HashMap中的Integer.highestOneBit((number - 1) << 1)來計算這個值
    //我的理解是concurrencyLevel一般都比較小(預設為16),採用這種計算方法效率更高。
    while (ssize < concurrencyLevel) {
        ++sshift;
        ssize <<= 1;
    }
    //後面根據hash計算segment位置時需要用到
    this.segmentShift = 32 - sshift;
    this.segmentMask = ssize - 1;
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    //計算每一個segment中table的length
    int c = initialCapacity / ssize;
    if (c * ssize < initialCapacity)
        ++c;
    int cap = MIN_SEGMENT_TABLE_CAPACITY;
    while (cap < c)
        cap <<= 1;
    // create segments and segments[0]
    Segment<K,V> s0 =
            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                    (HashEntry<K,V>[])new HashEntry[cap]);
    Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
    UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
    this.segments = ss;
}

和HashMap最大的不同就是多了Segment的初始化。

Segment的Size也初始化為2的N次方,這為後面的Map整體resize以及確定一個hash值所在Segment都提供簡便方法。

每個Segment中的table同HashMap中table一樣,接著來看PUT時怎麼計算Segment的位置。

PUT

public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
     //取得Key的Segment位置
    int j = (hash >>> segmentShift) & segmentMask;
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}

segmentShift:在構造函數中計算出來的,假設concurrencyLevel為16,segmentShift=28(32-4)

segmentMask:15(16-1)

可見求Key所在Segment的演算法和HashMap中求Key所在table中的位置一樣,都是 hash & (length-1)。

所以這裡Segment的length也必須是2的N次方。

hash >>> segmentShift是為了使用hash的高位進行與運算。

s.put方法,就是把Key放到Segment中table的響應位置,它的演算法和HashMap中類似,只是加入了鎖。

線程安全

HashMap - 非線程安全

Put一個Key時有下麵這段代碼:

    void createEntry(int hash, K key, V value, int bucketIndex) {
        //1.取得鏈表
        Entry<K,V> e = table[bucketIndex];
        //2.將新Key設置為鏈表的第一個
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

假設有兩個線程A、B,同時進行第1步,它們獲取到的e是同一個,如:x,y,z

然後線程A運行到第2步,為e添加了一個新元素a,並賦值給table[bucketIndex],此時table[bucketIndex]為:a,x,y,z

而後線程B運行到第2步,為e添加了一個新元素b,並賦值給table[bucketIndex],此時table[bucketIndex]為:b,x,y,z

所以這種情況下就會有問題,這隻是其中的一個例子,所以HashMap是非線程安全的。

ConcurrentHashMap - 線程安全

Put一個Key到Table時,使用如下代碼:

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                ......
            } finally {
                unlock();
            }
            return oldValue;
}

可以看到put時,加入了Lock,這就保證了線程的安全性。

查看ConcurrentHashMap源代碼可以發現,ConcurrentHashMap的remove、replace等有可能引起線程安全問題的地方都加了Lock。

ConcurrentHashMap的Get方法並不是完全線程安全,因為Get時沒有加鎖,但JDK用了很多volatile類型變數來保證在大多數情況下的線程安全。

具體怎麼線程不安全,參考:深入剖析ConcurrentHashMap

總結:

ConcurrentHashMap在絕大多數情況下是線程安全的,在多線程情況下請使用ConcurrentHashMap。

 

參考:

1. Java 8系列之重新認識HashMap http://tech.meituan.com/java-hashmap.html

2. 深入剖析ConcurrentHashMap http://ifeve.com/java-concurrent-hashmap-2

3. jdk8之ConcurrentHashMap源碼解析 http://jahu.iteye.com/blog/2331191


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

-Advertisement-
Play Games
更多相關文章
  • 在日常的開發中,運行定時任務基本上已經是很普遍的需求了,可以通過windows服務+timer組件來實現,也可以使用第三方框架來集成,Quartz.NET就是一款從JAVA的Quartz移植過來的一個不錯的作業調度組件,但是當我們把作業都寫好,並部署完成的時候,管理成為了很麻煩的事情,因此我基於Qu ...
  • 今日問題: 請問主程式輸出結果是什麼?(點擊以下“【Java每日一題】20161216”查看20161215問題解析) 題目原發佈於公眾號、簡書:【Java每日一題】20161216,【Java每日一題】20161216 註:weknow團隊近期開通並認證了分答,歡迎大家收聽,有問題也歡迎到分答來咨 ...
  • 本文面向php語言的laravel框架的用戶,介紹一些laravel框架裡面容器管理方面的使用要點。文章很長,但是內容應該很有用,希望有需要的朋友能看到。php經驗有限,不到位的地方,歡迎幫忙指正。 1. laravel容器基本認識 laravel框架是有一個容器框架,框架應用程式的實例就是一個超大 ...
  • 我要寫幾篇隨筆,為準備學習Java作為自己第一門電腦編程語言的同學總結我認為必需、但在一般的Java教材/課程中並不教授的預備知識。但我並不教你Java本身。我(但願可以)幫你弄明白隨便一本Java教材第一章第一節講的“標準版”、“企業版”、“虛擬機”、“垃圾回收”是什麼。理想的讀者是中學生、家庭 ...
  • ...
  • 電腦網路的分類: 區域網(LAN) 指在一個較小地理範圍內的各種電腦網路設備互聯在一起的通信網路,可以包括一個或多個子網,通常局限在幾千米的範圍之內。 城域網(MAN) 主要由城域範圍內的各個區域網之間互連構成。 廣域網(WAN) 由距離較遠的區域網與城域網互聯構成的通信網路,通常是除了電腦設 ...
  • 題目:古典問題:3個月起每個月都生一對兔子,小兔子長到第三個月後每個月又生一對兔子,假如兔子都不死,問每個月的兔子總數為多少? 分析:首先我們要明白題目的意思指的是每個月的兔子總對數;假設將兔子分為小中大三種,兔子從出生後三個月後每個月就會生出一對兔子, 那麼我們假定第一個月的兔子為小兔子,第二個月 ...
  • 1.實例變數和類變數 成員變數 VS 局部變數 局部變數(存儲在方法的棧記憶體中) 形參:方法簽名中定義,由方法調用者賦值,隨方法結束而消亡 方法內局部變數:方法內定義,必須在方法內進行顯示初始化,初始化完成後開始生效,隨方法結束而消亡 代碼塊內局部變數:代碼塊內定義,必須代碼塊內進行顯示初始化,初始 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...