最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java中的ConcurrentHashMap集合源碼解析

 更新時(shí)間:2023年11月15日 11:07:28   作者:龍三丶  
這篇文章主要介紹了Java中的ConcurrentHashMap集合源碼解析,ConcurrentHashMap底層容器和HashMap相同,同樣是Node數(shù)組+鏈表+紅黑樹(shù),不同的是在原來(lái)的基礎(chǔ)之上使用了Synchronized+CAS來(lái)保證線程安全,下面我們來(lái)進(jìn)行源碼分析,需要的朋友可以參考下

前言

在并發(fā)環(huán)境下,HashMap會(huì)出現(xiàn)線程安全問(wèn)題,HashMap的擴(kuò)容操作會(huì)出現(xiàn)閉環(huán)現(xiàn)象,當(dāng)調(diào)用get方法時(shí),會(huì)出現(xiàn)死循環(huán)。

所以,JDK給我們提供了另一個(gè)線程安全容器,ConcurrentHashMap。

在本章中我們來(lái)詳細(xì)探討JDK 1.8中ConcurrentHashMap的核心方法,且為什么是線程安全的以及和JDK 1.8之前的又有何區(qū)別。

ConcurrentHashMap底層容器和HashMap相同,同樣是Node數(shù)組+鏈表+紅黑樹(shù),不同的是在原來(lái)的基礎(chǔ)之上使用了Synchronized+CAS來(lái)保證線程安全,下面我們來(lái)進(jìn)行源碼分析。

put

public V put(K key, V value) {
		return putVal(key, value, false);
	}
 
	final V putVal(K key, V value, boolean onlyIfAbsent) {
		//從這我們可以看出ConcurrentHashMap的key和value不能為null
		if (key == null || value == null) throw new NullPointerException();
		//得到key的hash值
		int hash = spread(key.hashCode());
		//這是用來(lái)記錄鏈表的長(zhǎng)度
		int binCount = 0;
		//table:核心的Node數(shù)組。
		for (Node<K,V>[] tab = table;;) {
			Node<K,V> f; int n, i, fh;
			//如果數(shù)組為空,則進(jìn)行數(shù)組的初始化。這里相當(dāng)于懶漢模式,使用的時(shí)候才去初始化
			if (tab == null || (n = tab.length) == 0)
				//進(jìn)行數(shù)組的初始化
				tab = initTable();
			//根據(jù)key的hash計(jì)算出該key在Node數(shù)組中的位置,并判斷這個(gè)位置是否為null
			else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
				//當(dāng)前的數(shù)組位置為null,則使用CAS插入一個(gè)新的Node
				if (casTabAt(tab, i, null,
						new Node<K,V>(hash, key, value, null)))
					break;                   // no lock when adding to empty bin
			}
			//表示正在進(jìn)行擴(kuò)容操作 涉及了ForwardingNode 這個(gè)特殊的Node,
                        //在擴(kuò)容時(shí)會(huì)進(jìn)行創(chuàng)建,且固定傳入的hash值為 -1
			else if ((fh = f.hash) == MOVED)
				tab = helpTransfer(tab, f);
			//到了這里表示,出現(xiàn)了hash沖突,key在Node數(shù)組中的索引位置不為null。
			//需進(jìn)行鏈表或紅黑樹(shù)的插入操作。
			else {
				V oldVal = null;
				//這個(gè) f 存放是 根據(jù)key在數(shù)組中找到的Node,相當(dāng)于紅黑樹(shù)或鏈表的頭結(jié)點(diǎn),并進(jìn)行加鎖
				synchronized (f) {
					//這里再進(jìn)行一次判斷,保證f沒(méi)被其它線程修改
					if (tabAt(tab, i) == f) {
						//如果是鏈表
						if (fh >= 0) {
							//統(tǒng)計(jì)鏈表的長(zhǎng)度
							binCount = 1;
							//對(duì)f這個(gè)Node進(jìn)行累加鏈表的長(zhǎng)度,并遍歷鏈表
							for (Node<K,V> e = f;; ++binCount) {
								K ek;
								//如果存在相同的value,則覆蓋
								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;
								//遍歷到最后一個(gè)Node,插入一個(gè)新的Node到鏈表的尾部
								if ((e = e.next) == null) {
									pred.next = new Node<K,V>(hash, key,
											value, null);
									break;
								}
							}
						}
						//如果是紅黑樹(shù)
						else if (f instanceof TreeBin) {
							Node<K,V> p;
							binCount = 2;
							//使用紅黑樹(shù)的方式進(jìn)行Node的插入
							if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
									value)) != null) {
								oldVal = p.val;
								if (!onlyIfAbsent)
									p.val = value;
							}
						}
					}
				}
				if (binCount != 0) {
					//判斷鏈表的長(zhǎng)度是否大于TREEIFY_THRESHOLD,是則轉(zhuǎn)紅黑樹(shù)
					if (binCount >= TREEIFY_THRESHOLD)
						treeifyBin(tab, i);
					if (oldVal != null)
						return oldVal;
					break;
				}
			}
		}
		//統(tǒng)計(jì)整個(gè)的數(shù)組長(zhǎng)度,并判斷是否需要擴(kuò)容
		addCount(1L, binCount);
		return null;
	}

好了,以上就是大致的分析內(nèi)容,但還有許多步驟沒(méi)有展開(kāi)代碼詳細(xì)說(shuō)明,如初始化、鏈表轉(zhuǎn)紅黑樹(shù)及擴(kuò)容等,其中擴(kuò)容步驟非常復(fù)雜,有機(jī)會(huì)我會(huì)單獨(dú)寫(xiě)一篇博客詳細(xì)介紹,在這就不多說(shuō)了。我們接下來(lái)介紹較為簡(jiǎn)單的初始化及get方法。

初始化數(shù)組

private final Node<K,V>[] initTable() {
		Node<K,V>[] tab; int sc;
		while ((tab = table) == null || tab.length == 0) {
			//表示已經(jīng)有線程在進(jìn)行初始化操作,讓出CPU的執(zhí)行權(quán)
			if ((sc = sizeCtl) < 0)
				Thread.yield(); // lost initialization race; just spin
			//把sc賦值為 -1,表示當(dāng)前線程開(kāi)始執(zhí)行初始化操作
			else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
				try {
					if ((tab = table) == null || tab.length == 0) {
						//獲取數(shù)組的初始長(zhǎng)度,默認(rèn)為16
						int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
						@SuppressWarnings("unchecked")
						//初始化數(shù)組
						Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
						table = tab = nt;
						sc = n - (n >>> 2);
					}
				} finally {
					//sizeCtl 參數(shù) 第一個(gè)if判斷需要用到
					sizeCtl = sc;
				}
				break;
			}
		}
		return tab;
	}

get

public V get(Object key) {
		Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
		//計(jì)算key的hash
		int h = spread(key.hashCode());
		if ((tab = table) != null && (n = tab.length) > 0 &&
				//根據(jù)hash值做運(yùn)算獲取數(shù)組對(duì)應(yīng)的Node(相當(dāng)于頭結(jié)點(diǎn))
				(e = tabAt(tab, (n - 1) & h)) != null) {
			//根據(jù)hash和equals判斷該Node的key是否和get的key相等,是則返回value
			if ((eh = e.hash) == h) {
				if ((ek = e.key) == key || (ek != null && key.equals(ek)))
					return e.val;
			}
			//這里判斷是否正在擴(kuò)容 或者 該節(jié)點(diǎn)為紅黑樹(shù)
			else if (eh < 0)
				return (p = e.find(h, key)) != null ? p.val : null;
			//到了這一步表示該結(jié)構(gòu)為鏈表,遍歷鏈表,返回value
			while ((e = e.next) != null) {
				if (e.hash == h &&
						((ek = e.key) == key || (ek != null && key.equals(ek))))
					return e.val;
			}
		}
		return null;
	}

好了,核心的源碼部分就分析到這里,我們?cè)趤?lái)看看JDK 1.8之前的ConcurrentHashMap大致是怎么實(shí)現(xiàn)的,區(qū)別相當(dāng)大。

1.8之前的ConcurrentHashMap是采用了ReentrantLock+Segment+HashEntry的結(jié)構(gòu)

整體就是由一個(gè)個(gè) Segment 組成,Segment中包含了HashEntry數(shù)組,HashEntry數(shù)組就是1.8的那個(gè)table,只不過(guò)它這里是多個(gè)。

其中Segment在實(shí)現(xiàn)上繼承了ReentrantLock,這樣就自帶了鎖的功能,它在進(jìn)行put的時(shí)候只會(huì)鎖住一個(gè)Segment,所以理論上,最多可以同時(shí)支持 Segment 個(gè)數(shù)的線程并發(fā)寫(xiě),只要它們的操作分別分布在不同的 Segment 上。get的時(shí)候也是先找到一個(gè)Segment,然后在根據(jù)Hash值找到數(shù)組中的值。

至于為什么JDK在之后使用Synchronized來(lái)保證線程安全,是因?yàn)樵贘DK在版本迭代中一直在對(duì)Synchronized進(jìn)行優(yōu)化,使得Synchronized關(guān)鍵字在某些場(chǎng)景下已經(jīng)不比ReentrantLock效率慢,甚至更快。

到此這篇關(guān)于Java中的ConcurrentHashMap集合源碼解析的文章就介紹到這了,更多相關(guān)ConcurrentHashMap集合源碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Jenkins系統(tǒng)如何進(jìn)行數(shù)據(jù)備份

    Jenkins系統(tǒng)如何進(jìn)行數(shù)據(jù)備份

    隨著我們的長(zhǎng)期使用,Jenkins系統(tǒng)中的內(nèi)容會(huì)越來(lái)越多,特別是一些配置相關(guān)的東西,不能有任何丟失。這個(gè)時(shí)候我們就需要定期備份我們的Jenkins系統(tǒng),避免一些誤操作不小心刪除了某些重要文件,本文就將介紹下Jenkins系統(tǒng)如何進(jìn)行數(shù)據(jù)備份
    2021-06-06
  • Java判斷兩個(gè)浮點(diǎn)數(shù)相等

    Java判斷兩個(gè)浮點(diǎn)數(shù)相等

    本文主要介紹了Java判斷兩個(gè)浮點(diǎn)數(shù)相等,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-05-05
  • FeignMultipartSupportConfig上傳圖片配置方式

    FeignMultipartSupportConfig上傳圖片配置方式

    這篇文章主要介紹了FeignMultipartSupportConfig上傳圖片配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • 把Java程序轉(zhuǎn)換成exe,可直接運(yùn)行的實(shí)現(xiàn)

    把Java程序轉(zhuǎn)換成exe,可直接運(yùn)行的實(shí)現(xiàn)

    這篇文章主要介紹了把Java程序轉(zhuǎn)換成exe,可直接運(yùn)行的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-09-09
  • 解決SpringMVC Controller 接收頁(yè)面?zhèn)鬟f的中文參數(shù)出現(xiàn)亂碼的問(wèn)題

    解決SpringMVC Controller 接收頁(yè)面?zhèn)鬟f的中文參數(shù)出現(xiàn)亂碼的問(wèn)題

    下面小編就為大家分享一篇解決SpringMVC Controller 接收頁(yè)面?zhèn)鬟f的中文參數(shù)出現(xiàn)亂碼的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • java設(shè)計(jì)簡(jiǎn)單學(xué)生管理系統(tǒng)

    java設(shè)計(jì)簡(jiǎn)單學(xué)生管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了java設(shè)計(jì)簡(jiǎn)單學(xué)生管理系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • servlet簡(jiǎn)單實(shí)現(xiàn)文件下載的方法

    servlet簡(jiǎn)單實(shí)現(xiàn)文件下載的方法

    這篇文章主要介紹了servlet簡(jiǎn)單實(shí)現(xiàn)文件下載的方法,涉及基于servlet技術(shù)實(shí)現(xiàn)流形式文件傳輸?shù)南嚓P(guān)操作技巧,需要的朋友可以參考下
    2016-12-12
  • Java聊天室之實(shí)現(xiàn)聊天室服務(wù)端功能

    Java聊天室之實(shí)現(xiàn)聊天室服務(wù)端功能

    這篇文章主要為大家詳細(xì)介紹了Java簡(jiǎn)易聊天室之實(shí)現(xiàn)聊天室服務(wù)端功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以了解一下
    2022-10-10
  • IDEA中如何去掉Java方法注釋后的空行

    IDEA中如何去掉Java方法注釋后的空行

    本文介紹了如何在IntelliJIDEA中去掉Java方法注釋后多余的空行,并提供了詳細(xì)的操作步驟,通過(guò)修改設(shè)置,可以避免默認(rèn)格式化過(guò)程中自動(dòng)插入的空行,使Javadoc注釋更加緊湊和清晰,符合個(gè)人或團(tuán)隊(duì)的代碼規(guī)范
    2025-02-02
  • myeclipse導(dǎo)出可運(yùn)行jar包簡(jiǎn)介

    myeclipse導(dǎo)出可運(yùn)行jar包簡(jiǎn)介

    這篇文章主要介紹了myeclipse導(dǎo)出可運(yùn)行jar包簡(jiǎn)介,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-11-11

最新評(píng)論

温宿县| 柘荣县| 泗水县| 和政县| 江山市| 庆云县| 阿鲁科尔沁旗| 池州市| 公安县| 敦化市| 温州市| 二手房| 江孜县| 钟山县| 呼玛县| 峨山| 义乌市| 昌宁县| 常州市| 桃江县| 秀山| 杭锦后旗| 佛坪县| 广丰县| 江城| 行唐县| 晋江市| 新竹市| 永济市| 鹤岗市| 清丰县| 江门市| 南部县| 琼中| 读书| 富川| 徐水县| 邳州市| 集贤县| 宁安市| 五寨县|