电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

淺談Java源碼ConcurrentHashMap

瀏覽:104日期:2022-08-12 13:37:11
目錄一、記錄形式二、ConcurrentHashMap三、關鍵點一、記錄形式

打算直接把過程寫在源碼中,會按序進行注釋,查閱的時候可以按序號只看注釋部分

二、ConcurrentHashMap

直接模擬該類的使用過程,從而一步步看其怎么運作的吧,當然最好還是帶著問題一遍思考一遍總結會比較好,我閱讀源碼的時候帶著以下幾個問題

并發(fā)體現(xiàn)在哪里?怎么保證線程安全的 怎么擴容的?擴容是怎么保證線程安全的? 怎么put的?put是怎么保證線程安全的? 用了哪些鎖?這些鎖的作用是什么? 需要留意哪些關鍵點?

我們最簡單地使用方法是怎么樣的?

new一個ConcurrentHashMap對象 調用put方法放入k-v對 調用get方法獲取k-v對

那么很顯然,考慮只有在進行修改與更新時需要考慮并發(fā),所以我關注的重點在put方法與擴容上

首先new一個對象時,我們傳參入一個size,調用其只有一個參數(shù)的構造器

public ConcurrentHashMap(int initialCapacity) {if (initialCapacity < 0) throw new IllegalArgumentException();// 1、判斷傳入的大小是否超過最大值的一半,若超過則取最大值// 2、若沒超過,則調用tableSizeFor// 3、tableSizeFor可以讓size為2的倍數(shù),為什么要是2的倍數(shù)呢?因為對hash取模的時候// 用的是位運算,只有size為2的倍數(shù)才能這么做int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));this.sizeCtl = cap; }

// 1、判斷傳入的大小是否超過最大值的一半,若超過則取最大值// 2、若沒超過,則調用tableSizeFor// 3、tableSizeFor讓size為2的倍數(shù),為什么要是2的倍數(shù)呢?因為對hash取模的時候// 用的是位運算,只有size為2的倍數(shù)才能這么做

注意,此時并沒有創(chuàng)建map數(shù)據(jù)結構,所以ConcurrentHashMap是懶惰創(chuàng)建的

接著我們調用put方法放入數(shù)據(jù),put方法調的putVal

final V putVal(K key, V value, boolean onlyIfAbsent) { // 1、k-v都不能為空,不然拋異常if (key == null || value == null) throw new NullPointerException();// 2、獲取key的hashcode的hash值int hash = spread(key.hashCode());// 3、使用binCount來統(tǒng)計鏈表有多少個元素的,便于后面判斷是否需要變成紅黑樹int binCount = 0;// 4、創(chuàng)建tab臨時變量,并賦值為table,由于還沒初始化,值為null// 這里注意了,table的類型是Node數(shù)組,這個Node其實就是Map.Entry<K,V>for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; // 5、判斷tab為空后才進行初始化,初始化完成后重新進入循環(huán) if (tab == null || (n = tab.length) == 0)tab = initTable(); // 6、此時已經初始化好了,可以開始插入數(shù)據(jù)。 // 這里用tabAt(tab,i)獲取tab的第i個下標上的鏈表指針 // 注意了,(n-1)& hash其實就是hash%n,只有n為2的次方才能這么做 // 位運算可以提升效率 // 7、f就是獲取到的第i個位置的鏈表頭指針 // 如果為null說明什么都沒有,可以嘗試插入元素 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 8、考慮插入那就要考慮并發(fā)了,casTab表示用cas方法進行插入 // 插入一個新的Node結點,這個是能夠保證線程安全的 // 我們知道保證線程安全除了用cas之外還不夠,還需要保證操作對象的可見性 // 在這里是對tab進行操作,tab在前面用table賦值,而table是加了volatile的 // 所以沒毛病哈if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } // 9、如果f不為空,并且f.hash==MOVED(-1),說明當前這個位置正在被移動 // 說明有線程在擴容,那么當然不能這時候還去插入了,這里調用helpTransfer去幫助擴容 // 注意了,這意味著擴容時是一個一個位置來移動的,每移動一個就將f.hash改成MOVED // 也就意味著如果當前線程想要操作的位置還沒有被移動時是可以操作的,這使得并發(fā)度更高了 else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f); // 10、如果f.hash表示沒有被移動,且f不為null說明可以這個位置已經有東西了 // 需要對其遍歷,并找到合適的位置插入 else {V oldVal = null;// 11、由于要進行插入了,所以得加鎖,注意了哈,這里用的synchronized// 并且鎖住的是當前位置對象(不一定是鏈表也可能是樹)// 意味著除了當前線程,其他線程都不準操作了哈// 如果這時候正在擴容的線程擴到這里了,會被阻塞的哈synchronized (f) {// 確定f為起點 if (tabAt(tab, i) == f) { // 12、判斷f.hash是大于0,大于0表示當前這個東西是鏈表 // 下面執(zhí)行鏈表的更新操作if (fh >= 0) { binCount = 1; // 13、接著就是具體的遍歷鏈表,查找是否對應值是否存在 // 如果遍歷完都找不到,那么就在尾部插入新的結點 // 注意了哈這就是尾插 // 14、同時,每遍歷一個結點還要累加binCount // 即統(tǒng)計一下當前鏈表個數(shù),便于后面轉紅黑樹判斷 for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { 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;} }}// 13、如果f對應的是樹結構,那就執(zhí)行樹的更新方法else if (f instanceof TreeBin) { Node<K,V> p; binCount = 2; if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {oldVal = p.val;if (!onlyIfAbsent) p.val = value; }} }}// 14、前面說了哈,binCount就是鏈表元素個數(shù),接著就判斷是否大于閾值// 大于則轉樹,可以看這個閾值TREEIFY_THRESHOLD=8if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i); if (oldVal != null)return oldVal; break;} }}// 15、addCount是讓size+1的// 這里要注意,加法也是分多步的,需要先get才能+,因此為了保證線程安全也是需要用cas的// 這里加size的方式并不是直接往size上加,因為size是經常修改的,如果經常訪問的話效率很低// 那么做法和LongAdder這一原子累加器類似,用一個CountCell數(shù)組,每個線程只操作數(shù)組中的// 某一個Cell,最后匯總即可addCount(1L, binCount);return null; }

總結一下put的過程

1.先判斷map是否創(chuàng)建,沒創(chuàng)建則先創(chuàng)建,結構為 Node<K,V>[ ] extend Map.Entry

2.接著找key應該放在哪個位置 i

3.判斷該位置是否為空,為空則使用CAS插入一個新的Node

4.不為空則判斷當前位置狀態(tài)是否為MOVED,是則說明當前位置正在被其他線程改動,當前線程需要幫助完成移動

5.不為空且不為MOVED,則判斷是鏈表還是樹,分別執(zhí)行對應的更新方法

6.如果為鏈表

先對鏈表上鎖,用syn 則遍歷并查找是否已存在 找到最后都不存在則直接尾插 同時統(tǒng)計鏈表上的元素

7.判斷鏈表元素是否超過變成樹的閾值 8 ,超過則直接變成樹,變成樹也是加syn

8.使用addCount更新size,更新方式類似LongAdder

三、關鍵點 懶惰加載map 對當前位置操作之前需要判斷當前位置的存儲的內容是否被其他線程移動了,如果被移動則先去幫助完成移動

執(zhí)行擴容移動的線程是挨個移動每個位置的鏈表,移動前會先將當前位置的狀態(tài)用CAS改成MOVED

注意了這個是提升并發(fā)度的關鍵所在

因為插入和移動(擴容)的粒度是細化到每個位置的鏈表上

意味著擴容和插入可以同時進行

意味著插入時上鎖后,擴容線程執(zhí)行到該位置需要阻塞

而不是直接整個map都鎖住

當前位置沒內容時,通過CAS插入新Node,而操作鏈表時用的是syn

如果面試問到ConcurrentHashMap中用了什么鎖就心中有數(shù)了

更新size的時候用的是LongAdder類似的方法

有一個CountCell數(shù)組,每個線程更新后,對數(shù)組中的某個Cell+1

如果沒有競爭則只有一個baseCell,對其+1

統(tǒng)計size時匯總即可

再細化一下前面的流程思考初始化map的時候怎么保證線程安全?防止多個線程同時初始化?答案在initTable方法中

可以看到,sizeCtl如果是負數(shù)說明正在擴容或者初始化 因此當需要初始化時,就去CAS地改變sizeCtl,將其變?yōu)樨摂?shù) 哪個線程CAS成功,則可以執(zhí)行初始化,這就保證了線程安全 可以再去看看,sizeCtl是volatile修飾的哈 并且SIZECTL是sizeCtl的offset,這些都是原子類類似的東西了

private final Node<K,V>[] initTable() {Node<K,V>[] tab; int sc;while ((tab = table) == null || tab.length == 0) { if ((sc = sizeCtl) < 0)Thread.yield(); // lost initialization race; just spin else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {try { if ((tab = table) == null || tab.length == 0) {int n = (sc > 0) ? sc : DEFAULT_CAPACITY;@SuppressWarnings('unchecked')Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = tab = nt;sc = n - (n >>> 2); }} finally { sizeCtl = sc;}break; }}return tab; }

get方法就不說了,因為不涉及并發(fā),就是查找而已感覺差不多了,把put方法搞清楚了,ConcurrentHashMap怎么解決線程安全的也清楚了,并且并發(fā)關鍵點在哪也清楚了

到此這篇關于淺談Java源碼ConcurrentHashMap的文章就介紹到這了,更多相關Java ConcurrentHashMap內容請搜索好吧啦網以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Java
相關文章:
主站蜘蛛池模板: 活性氧化铝|无烟煤滤料|活性氧化铝厂家|锰砂滤料厂家-河南新泰净水材料有限公司 | 植筋胶-粘钢胶-碳纤维布-碳纤维板-环氧砂浆-加固材料生产厂家-上海巧力建筑科技有限公司 | 窖井盖锯圆机_锯圆机金刚石锯片-无锡茂达金刚石有限公司 | 伺服电机维修、驱动器维修「安川|三菱|松下」伺服维修公司-深圳华创益 | 杭州标识标牌|文化墙|展厅|导视|户内外广告|发光字|灯箱|铭阳制作公司 - 杭州标识标牌|文化墙|展厅|导视|户内外广告|发光字|灯箱|铭阳制作公司 | 恒温水槽与水浴锅-上海熙浩实业有限公司| 刘秘书_你身边专业的工作范文写作小秘书 | SMN-1/SMN-A ABB抽屉开关柜触头夹紧力检测仪-SMN-B/SMN-C-上海徐吉 | 变色龙PPT-国内原创PPT模板交易平台 - PPT贰零 - 西安聚讯网络科技有限公司 | 礼堂椅厂家|佛山市艺典家具有限公司| 集菌仪厂家_全封闭_封闭式_智能智能集菌仪厂家-上海郓曹 | 台湾HIWIN上银直线模组|导轨滑块|TBI滚珠丝杆丝杠-深圳汉工 | 河南mpp电力管_mpp电力管生产厂家_mpp电力电缆保护管价格 - 河南晨翀实业 | 户外环保不锈钢垃圾桶_标识标牌制作_园林公园椅厂家_花箱定制-北京汇众环艺 | 超声波分散机-均质机-萃取仪-超声波涂料分散设备-杭州精浩 | 无线对讲-无线对讲系统解决方案-重庆畅博通信 | 有机废气处理-rto焚烧炉-催化燃烧设备-VOC冷凝回收装置-三梯环境 | 铝机箱_铝外壳加工_铝外壳厂家_CNC散热器加工-惠州市铂源五金制品有限公司 | YAGEO国巨电容|贴片电阻|电容价格|三星代理商-深圳市巨优电子有限公司 | 杭州|上海贴标机-百科| nalgene洗瓶,nalgene量筒,nalgene窄口瓶,nalgene放水口大瓶,浙江省nalgene代理-杭州雷琪实验器材有限公司 | 有福网(yofus.com)洗照片冲印,毕业聚会纪念册相册制作个性DIY平台 | 焊缝跟踪系统_激光位移传感器_激光焊缝跟踪传感器-创想智控 | 诚暄电子公司首页-线路板打样,pcb线路板打样加工制作厂家 | 石家庄小程序开发_小程序开发公司_APP开发_网站制作-石家庄乘航网络科技有限公司 | jrs高清nba(无插件)直播-jrs直播低调看直播-jrs直播nba-jrs直播 上海地磅秤|电子地上衡|防爆地磅_上海地磅秤厂家–越衡称重 | 瓶盖扭矩测试仪-瓶盖扭力仪-全自动扭矩仪-济南三泉中石单品站 | HV全空气系统_杭州暖通公司—杭州斯培尔冷暖设备有限公司 | 二手Sciex液质联用仪-岛津气质联用仪-二手安捷伦气质联用仪-上海隐智科学仪器有限公司 | 龙门加工中心-数控龙门加工中心厂家价格-山东海特数控机床有限公司_龙门加工中心-数控龙门加工中心厂家价格-山东海特数控机床有限公司 | 伺服电机维修、驱动器维修「安川|三菱|松下」伺服维修公司-深圳华创益 | 下水道疏通_管道疏通_马桶疏通_附近疏通电话- 立刻通 | 糖衣机,除尘式糖衣机,全自动糖衣机,泰州市长江制药机械有限公司 体感VRAR全息沉浸式3D投影多媒体展厅展会游戏互动-万展互动 | 苏州伊诺尔拆除公司_专业酒店厂房拆除_商场学校拆除_办公楼房屋拆除_家工装拆除拆旧 | 美甲贴片-指甲贴片-穿戴美甲-假指甲厂家--薇丝黛拉 | 工业机械三维动画制作 环保设备原理三维演示动画 自动化装配产线三维动画制作公司-南京燃动数字 聚合氯化铝_喷雾聚氯化铝_聚合氯化铝铁厂家_郑州亿升化工有限公司 | 成都办公室装修-办公室设计-写字楼装修设计-厂房装修-四川和信建筑装饰工程有限公司 | 智慧农业|农业物联网|现代农业物联网-托普云农物联网官方网站 | 钛合金标准件-钛合金螺丝-钛管件-钛合金棒-钛合金板-钛合金锻件-宝鸡远航钛业有限公司 | 篮球架_乒乓球台_足球门_校园_竞技体育器材_厂家_价格-沧州浩然体育器材有限公司 | 塑胶跑道_学校塑胶跑道_塑胶球场_运动场材料厂家_中国塑胶跑道十大生产厂家_混合型塑胶跑道_透气型塑胶跑道-广东绿晨体育设施有限公司 |