ConcurrentHashMap詳解 什么時候CAS什么時候synchronized

jdk:1.7 segment數組+hashEntry數組+鏈表實現

jdk版本:1.8:hashEntry+數組+紅黑樹實現

1、基本參數

//**1、最大容量**  hashmap的最大容量也是這個,菜鳥一面被問到了
private static final int MAXIMUM_CAPACITY = 1 << 30;//數組默認為16
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;//float浮點數,LOAD_FACTOR為final 不可更改
private static final float LOAD_FACTOR = 0.75f;

2、初始化1

也就是說不是你輸入什么就是什么容量,內部還有一個算法優化用戶的輸入。(防止用戶輸入參數太差)  
//邏輯:如果init>max的一半,則返回max,否則返回tableSizeFor(init + (initialCapacity >>> 1) + 1)); 
public ConcurrentHashMap(int init) {if (init < 0)throw new IllegalArgumentException();int cap = ((init >= (MAXIMUM_CAPACITY >>> 1)) ?MAXIMUM_CAPACITY :tableSizeFor(init + (init >>> 1) + 1));this.sizeCtl = cap;    //`sizeCtl` 表示容量
}//c=2返回2,c=3返回4,c=9返回16
//tableSizeFor 方法通過一系列位操作,將輸入值 c 轉換為大于或等于 c 的最小 2 的冪次方。這是為了確保哈希表的數組大小總是 2 的冪次方,從而優化哈希分布和查找性能。private static final int tableSizeFor(int c) {int n = c - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

初始化2

 public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException();if (initialCapacity < concurrencyLevel)   // Use at least as many binsinitialCapacity = concurrencyLevel;   // as estimated threads//非常神奇啊,傳入的loadFactor不會改變裝填因子,但是會改變傳入的容量參數,影響最終容量long size = (long)(1.0 + (long)initialCapacity / loadFactor);int cap = (size >= (long)MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : tableSizeFor((int)size);this.sizeCtl = cap;}

3、sizeCtl 不太理解

sizeCtlConcurrentHashMap 中一個非常重要且復雜的控制字段,其作用有很多個。

addCount()里面判斷是否要擴容是和sizeCtl比較,而不是和裝填因子比較。為什么使用 sizeCtl 而不是直接使用負載因子?

  1. 簡化計算
    • 通過將負載因子的計算隱式地包含在 sizeCtl 中,可以避免每次插入或刪除元素時重新計算負載因子,從而減少了計算開銷。
  2. 并發控制
    • 使用 sizeCtl 可以有效地協調多個線程同時進行擴容操作。負值表示正在擴容,并且包含擴容的狀態信息。這樣,可以避免多個線程重復觸發擴容。
  3. 性能優化
    • 在高并發環境下,頻繁訪問和修改共享變量(如負載因子)會帶來性能瓶頸。通過使用 sizeCtl,可以減少這種共享變量的訪問次數,從而提高性能。
// Unsafe mechanicsprivate static final long SIZECTL;
//初始化map時候,this.sizectl = cap; //cap為容量//比如public ConcurrentHashMap(Map<? extends K, ? extends V> m) {this.sizeCtl = DEFAULT_CAPACITY;putAll(m);}在擴容期間:當擴容開始時,sizeCtl 被設置為一個負值,表示當前參與擴容的線程數量。這樣其他線程可以知道表正在擴容并可以協同進行擴容操作。

4、put 重點

public V put(K key, V value) {return putVal(key, value, false);}/** Implementation for put and putIfAbsent */final V putVal(K key, V value, boolean onlyIfAbsent) {//1、數據檢查if (key == null || value == null) throw new NullPointerException();//2、求key哈希int hash = spread(key.hashCode());int binCount = 0;  //記錄遍歷的節點數,可以用于判斷是否要鏈表轉化為紅黑樹for (Node<K,V>[] tab = table;;) {  //死循環Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0)  //檢查 table 是否初始化tab = initTable();//使用哈希值計算索引 i 并檢查該位置是否為空。如果為空,使用 CAS 操作插入新節點,并跳出循環。else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break;                   // no lock when adding to empty bin}else if ((fh = f.hash) == MOVED) // MOVEDd標志用于判斷是否已經節點遷移//當一個桶(bin)中的所有節點都被遷移到新的數組中后,原來的位置上會放置一個特殊的轉發節點,表示這個桶已經處理完畢。此時,轉發節點的 hash 字段會被設置為 MOVED(即 -1)。tab = helpTransfer(tab, f);//協助遷移else {   //如果碰撞了  需要使用synchronized,放棄cas,f是table那個碰撞節點V oldVal = null;synchronized (f) {if (tabAt(tab, i) == f) { // 經典的雙重檢查,防止當前線程獲取table鎖之前,tabAt(tab, i)被其它線程改變了if (fh >= 0) {// 哈希值>=0代表是鏈表,<0代表是紅黑樹binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {  // 三比較,hashcode==hashcode,key==key,key.equals(key)oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key,value, null);break;}}}else if (f instanceof TreeBin) { // 紅黑樹Node<K,V> p;binCount = 2;  //binCount 被初始化為 2,因為紅黑樹中的節點數計算方式不同于鏈表。 具體原因我不知道if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}if (binCount != 0) {   // 判斷是否要擴容 TREEIFY_THRESHOLD=8if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);  //計數 里面通過cas維護元素個數。return null;}

總結:

  1. 判斷key value是否合法 判null
  2. 求key哈希
  3. 判斷table是否初始化,如果沒有就初始化
  4. 找到key哈希在table中的位置,判斷是否為null
    1. 如果是Null則cas直接添加節點
    2. 如果碰撞了 需要使用synchronized(f){},放棄cas,f是table那個碰撞節點
      1. 如果f.hash>=0,則說明是鏈表,遍歷查找,當(hashhash && keykey && key.equals(key))的時候返回元素。下一個節點為null還沒有找到,則插入節點
      2. 如果f.hash<0, 則說明是紅黑樹,遍歷查找。找不到就插入。
    3. 判斷是否要轉化為紅黑樹
  5. 調用addCount()計算map總的元素個數,內部通過cas來實現。
    1. 這里面會檢查table要不要擴容

**5、remove(Object key) **和put差不多

 final V replaceNode(Object key, V value, Object cv) {int hash = spread(key.hashCode());for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0 ||(f = tabAt(tab, i = (n - 1) & hash)) == null)    // 空直接返回 沒得刪break;else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);else {          //不空則鎖起來 再遍歷 找到就刪V oldVal = null;boolean validated = false;synchronized (f) {if (tabAt(tab, i) == f) {if (fh >= 0) {  // hash>=0 鏈表validated = true;for (Node<K,V> e = f, pred = null;;) {K ek;if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {V ev = e.val;if (cv == null || cv == ev ||(ev != null && cv.equals(ev))) {oldVal = ev;if (value != null)e.val = value;else if (pred != null)pred.next = e.next;elsesetTabAt(tab, i, e.next);}break;}pred = e;if ((e = e.next) == null //找不到break;}}else if (f instanceof TreeBin) {validated = true;TreeBin<K,V> t = (TreeBin<K,V>)f;TreeNode<K,V> r, p;if ((r = t.root) != null &&(p = r.findTreeNode(hash, key, null)) != null) {V pv = p.val;if (cv == null || cv == pv ||(pv != null && cv.equals(pv))) {oldVal = pv;if (value != null)p.val = value;else if (t.removeTreeNode(p))setTabAt(tab, i, untreeify(t.first));}}}}}if (validated) {     // 計數if (oldVal != null) {if (value == null)addCount(-1L, -1);return oldVal;}break;}}}return null;}

6、get(Object key)

public V get(Object key) {// 定義一些局部變量Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;// 計算鍵的哈希值,并進行哈希值擴散以減少碰撞int h = spread(key.hashCode());// 如果哈希表不為空并且哈希表長度大于0if ((tab = table) != null && (n = tab.length) > 0 &&// 計算哈希表索引,取出對應位置的節點(e = tabAt(tab, (n - 1) & h)) != null) {// 如果找到的節點的哈希值等于目標哈希值if ((eh = e.hash) == h) {// 并且鍵也相等(引用相等或 equals 相等),則返回該節點的值if ((ek = e.key) == key || (ek != null && key.equals(ek)))return e.val;}// 如果節點的哈希值小于0,表示該節點是一個特殊節點(如紅黑樹節點或轉移節點)else if (eh < 0)// 調用 find 方法在該節點中查找目標鍵對應的值return (p = e.find(h, key)) != null ? p.val : null;// 否則,遍歷該鏈表中的所有節點while ((e = e.next) != null) {if (e.hash == h &&((ek = e.key) == key || (ek != null && key.equals(ek))))return e.val;}}// 如果沒有找到,返回 nullreturn null;
}

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

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

相關文章

《科技與健康》是什么級別的期刊?是正規期刊嗎?能評職稱嗎?

問題解答 問&#xff1a;《科技與健康》期刊萬方網可查嗎 答&#xff1a;萬方、維普可查 問&#xff1a;《科技與健康》是正規期刊嗎&#xff1f; 答&#xff1a;萬方維普收錄的正規期刊。主管單位&#xff1a;長江出版傳媒股份有限公司 主辦單位&#xff1a;湖北科學技術…

孩子出生后為什么要做聽力篩查?

孩子出生后為什么要做聽力篩查&#xff1f; 新生兒聽力篩查&#xff0c;就是對所有新生兒在盡早的時間&#xff08;出生48小時后&#xff09;進行系統的聽力篩查測試。據相關文獻報道&#xff0c;在我國&#xff0c;正常分娩的新生兒聽力障礙的發生率約為0.1&#xff5e;0.3%&a…

機場專用手持激光驅鳥器原理及優勢

在機場的驅鳥工作中&#xff0c;各類驅鳥設備共同構建起一道堅不可摧的防線&#xff0c;以保障航班的安全起降。其中激光驅鳥器以其卓越的性能和顯著效果&#xff0c;在機場鳥擊防治中發揮著至關重要的作用。 激光驅鳥器&#xff0c;分為大型自動式和小型手持式&#xff0c;其有…

Python 技能提升(二)

理想的類結構 Property裝飾器 # 傳統寫法 class Square1:def __init__(self):self.__side Nonedef get_side(self):return self.__sidedef set_side(self, side):assert side > 0, 邊長不能為負數&#xff01;self.__side sidedef del_side(self):# del self.__sideself.…

「前端+鴻蒙」核心技術HTML5+CSS3(十)

1、H5簡介 H5是HTML5的簡稱,是構建現代網站和網絡應用的標準標記語言。HTML5新增了許多功能,包括更好的多媒體支持、新的表單控件、繪圖功能以及對響應式設計的改進。 2、H5產品布局 移動端H5網站布局通常使用流體布局或彈性盒模型(Flexbox),以適應不同屏幕尺寸。 示例…

2024年有什么值得入手的5G長期套餐大流量卡推薦?大流量手機卡入手指南(超4款正規手機卡實測總結)

前言 24年有什么值得入手的5G大流量卡推薦&#xff1f;大流量手機卡入手指南&#xff08;超4款正規手機卡實測總結&#xff09; 四大運營商有哪些大流量卡&#xff0c;可電話&#xff0c;非物聯網卡 所有卡激活后&#xff0c;均可以在官方app可查、 所有都是優惠長期 5G大流…

Python-匿名函數

一、概念 匿名函數造出來的是一個內存地址&#xff0c;且內存地址沒有綁定任何名字&#xff0c;很快被當做垃圾清理掉。所以匿名函數只需要臨時調用一次&#xff0c;而有名函數永久使用&#xff1b; 匿名函數一般和其他函數配合使用&#xff1b; # 有名函數def func(x, y):…

抖音直播統計、直播間無人互動直播效果軟件--抖音大師!

抖音大師介紹 抖音大師是抖音直播統計、直播間無人互動直播效果軟件&#xff0c;通過它&#xff0c;你可以快速添加直播互動效果&#xff01;軟件使用C#開發&#xff0c;無論是內存占用還是執行效果都遠比同行的效果高太多&#xff01;&#xff01;電腦所需性能大大降低&#x…

內聯匯編簡介

在C語言中嵌入匯編&#xff08;Assembly&#xff09;代碼&#xff0c;可以使用內聯匯編&#xff08;Inline Assembly&#xff09;&#xff0c;這在一些需要精確控制硬件或者優化性能的場合非常有用 以下是關于ASM語法的介紹&#xff0c;主要基于GCC&#xff08;GNU Compiler C…

做軟件測試需要懂代碼嗎?

隨著大數據、機器學習時代的到來&#xff0c;不少人有了“測試不需要懂代碼&#xff0c;那我就試試”的想法。這就引發了一系列疑問&#xff1a;不懂代碼可以做測試嗎&#xff1f;測試人員到底需不需要懂代碼&#xff1f;測試人員需要寫代碼嗎&#xff1f; 其實&#xff0c;在…

精準檢測,可燃氣體報警系統的技術原理與特點

在現代化的工業生產與日常生活中&#xff0c;可燃氣體泄露事故頻發&#xff0c;給人們的生命和財產安全帶來了嚴重威脅。 因此&#xff0c;可燃氣體報警檢測系統的應用變得尤為重要。它不僅能夠實時監測環境中的可燃氣體濃度&#xff0c;還能在發現異常情況時及時報警&#xf…

記 Codes 開源免費研發管理平臺 —— 生成式全局看板的創新實現

繼上一回合瀑布與敏捷的融合創新實現后&#xff0c;本篇我們來講一講 Codes 生成式全局看板的創新實現。 市面上所有的研發管理軟件&#xff0c;看板模式的項目&#xff0c;都是物理看板的電子化&#xff0c;好像也沒什么問題&#xff0c;但是在使用過程中體驗非常不好&#xf…

WebSocket和HTTP協議對比

WebSocket和HTTP是兩種不同的通信協議&#xff0c;它們在多個方面存在顯著差異&#xff0c;主要區別包括&#xff1a; 通信模式&#xff1a; HTTP 是一種無狀態的、基于請求-響應模型的協議。這意味著通信總是由客戶端發起請求&#xff0c;服務器被動響應。每次請求和響應都是獨…

使用 zxing 生成二維碼以及條形碼

需求背景 前期在做項目的時候&#xff0c;有一個需求是說要生成一張條形碼&#xff0c;并且呢將條形碼插入到 excel 中去&#xff0c;但是之前一直沒有搞過找個條形碼或者是二維碼&#xff0c;最后是做出來了&#xff0c;這里呢就先看看怎么生成&#xff0c;后面再抽時間來寫寫…

一條SQL語句的執行究竟經歷了哪些過程

在數據庫管理系統(DBMS)中,一條SQL語句的執行過程復雜且精細,從用戶輸入到獲取結果,中間需要經過多個步驟和組件的協同工作。這些步驟包括解析、優化、執行和結果返回等。以下是SQL語句執行過程的詳細分析: 1. 客戶端連接 連接建立: 用戶通過客戶端(如應用程序、SQL客戶…

掌握Element UI:加速你的網頁設計過程!

Element UI 是一套為開發者、UI/UX設計師和產品經理準備的采用Vue 2.0作為基礎框架實現的組件庫&#xff0c;提供配套的設計資源&#xff0c;可以幫助設計快速成型。即時設計也內置Element UI Kit資源&#xff0c;但有些小伙伴還是對此不太了解&#xff0c;接下來本文會詳細帶你…

antd-vue - - - - - a-select結合i18n使用(踩坑問題)

antd-vue - - - - - a-select結合i18n使用&#xff08;踩坑問題&#xff09; 1. 當前代碼 & 效果2. 解決辦法 1. 當前代碼 & 效果 <a-selectv-model:value"formState.quickSwitching":options"quickSwitchingOptions"search"handleSearch…

vue3+element-plus 表單校驗和循環form表單校驗

1.HTML頁面 //el-form 標簽添加上 ref"form2Form" :rules"rules2" :model"form2" 正常表單校驗 //沒有循環表單的使用事例<el-form-item label"投保人名稱" class"insurance-date-no1" prop"tbrName">…

什么是增值稅通俗的理解

增值稅的目的是為了對商品或服務在生產過程中增加的價值進行征稅。通俗地說&#xff0c;就是每當商品或服務在生產和銷售過程中“增值”了一次&#xff0c;政府就要對這部分增值收稅。 舉個例子&#xff0c;假設一個農場主種了小麥&#xff0c;然后賣給了面粉廠。面粉廠將小麥加…

29、親身體驗Young GC風暴:模擬教程帶你走進GC的神秘世界!

29.1、前文回顧 在今天的文章,我們將通過代碼演示來展示年輕代的Young GC是如何發生的。同時,我們還將指導大家如何在JVM參數中配置打印對應的GC日志。接下來,我們將通過分析GC日志,逐步解析JVM的垃圾回收機制是如何運作的。 29.2、不可不知的JVM參數設置技巧 首先,根據…