JDK1.7的ConcurrentHashMap源碼解析
概述
HashMap是非線程安全的,而HashTable是線程安全的,但是HashTable實(shí)現(xiàn)同步的方法比較暴力,即在所有的方法體上添加synchronized關(guān)鍵字
相當(dāng)于所有讀寫(xiě)線程均去讀取一把鎖,從并發(fā)角度,HashTable其實(shí)無(wú)法滿足較高的并發(fā)度。
另一種同步Map的方法是使用Collections工具類(lèi)。
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
/**
* @serial include
*/
private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L;
private final Map<K,V> m; // Backing Map
//互斥鎖
final Object mutex; // Object on which to synchronize
SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
}
SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}
public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}
public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}
public void putAll(Map<? extends K, ? extends V> map) {
synchronized (mutex) {m.putAll(map);}
}
public void clear() {
synchronized (mutex) {m.clear();}
}
private transient Set<K> keySet;
private transient Set<Map.Entry<K,V>> entrySet;
private transient Collection<V> values;
public Set<K> keySet() {
synchronized (mutex) {
if (keySet==null)
keySet = new SynchronizedSet<>(m.keySet(), mutex);
return keySet;
}
}
public Set<Map.Entry<K,V>> entrySet() {
synchronized (mutex) {
if (entrySet==null)
entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
return entrySet;
}
}
public Collection<V> values() {
synchronized (mutex) {
if (values==null)
values = new SynchronizedCollection<>(m.values(), mutex);
return values;
}
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return m.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return m.hashCode();}
}
public String toString() {
synchronized (mutex) {return m.toString();}
}
// Override default methods in Map
@Override
public V getOrDefault(Object k, V defaultValue) {
synchronized (mutex) {return m.getOrDefault(k, defaultValue);}
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
synchronized (mutex) {m.forEach(action);}
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
synchronized (mutex) {m.replaceAll(function);}
}
@Override
public V putIfAbsent(K key, V value) {
synchronized (mutex) {return m.putIfAbsent(key, value);}
}
@Override
public boolean remove(Object key, Object value) {
synchronized (mutex) {return m.remove(key, value);}
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
synchronized (mutex) {return m.replace(key, oldValue, newValue);}
}
@Override
public V replace(K key, V value) {
synchronized (mutex) {return m.replace(key, value);}
}
@Override
public V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
synchronized (mutex) {return m.computeIfAbsent(key, mappingFunction);}
}
@Override
public V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
synchronized (mutex) {return m.computeIfPresent(key, remappingFunction);}
}
@Override
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
synchronized (mutex) {return m.compute(key, remappingFunction);}
}
@Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
synchronized (mutex) {return m.merge(key, value, remappingFunction);}
}
private void writeObject(ObjectOutputStream s) throws IOException {
synchronized (mutex) {s.defaultWriteObject();}
}
}這種方法與HashTable實(shí)現(xiàn)方式類(lèi)似,也是鎖住整表來(lái)實(shí)現(xiàn)同步的。而ConcurrentHashMap則避免了上述兩種Map同步方式鎖住全表的問(wèn)題。
ConcurrentHashMap可以做到讀取數(shù)據(jù)不加鎖,并且其內(nèi)部的結(jié)構(gòu)可以讓其在進(jìn)行寫(xiě)操作的時(shí)候能夠?qū)㈡i的粒度保持盡量的小,不用對(duì)整個(gè)ConcurrentHashMap加鎖。
ConcurrentHashMap內(nèi)部結(jié)構(gòu)
ConcurrentHashMap內(nèi)部采用了一種叫segment的數(shù)據(jù)結(jié)構(gòu),很明顯它就是一個(gè)哈希桶數(shù)組,數(shù)組的元素就是HashEntry。

ConcurrentHashMap比HashMap多了一次hash過(guò)程,第一次hash定位到Segment,第二次hash定位到HashEntry,然后鏈表搜索找到指定節(jié)點(diǎn)。
該實(shí)現(xiàn)方法的缺點(diǎn)是hash過(guò)程比普通的HashMap要長(zhǎng),但是優(yōu)點(diǎn)也很明顯,在進(jìn)行寫(xiě)操作時(shí),只需鎖住寫(xiě)元素所在的Segment即可,其他Segment無(wú)需加鎖,提高了并發(fā)讀寫(xiě)的效率。
Segment
Segment繼承了ReentrantLock并實(shí)現(xiàn)了序列化接口,說(shuō)明Segment的鎖是可以重入的。

static final class Segment<K,V> extends ReentrantLock implements Serializable {
transient volatile HashEntry<K,V>[] table;
transient int count;
transient int modCount;
transient int threshold;
final float loadFactor;- count:Segment中元素的數(shù)量
- modCount:對(duì)table的大小造成影響的操作的數(shù)量(比如put或者remove操作)
- threshold:擴(kuò)容閾值
- table:鏈表數(shù)組,數(shù)組中的每一個(gè)元素代表了一個(gè)鏈表的頭部
- loadFactor:負(fù)載因子
Segment的數(shù)據(jù)結(jié)構(gòu)與普通的HashMap基本類(lèi)似,只是通過(guò)繼承ReentrantLock可實(shí)現(xiàn)線程安全的操作。
HashEntry
Segment中的元素是以HashEntry的形式存放在鏈表數(shù)組中的,其結(jié)構(gòu)與普通HashMap的HashEntry基本一致,不同的是Segment的HashEntry的value由volatile修飾,以支持內(nèi)存可見(jiàn)性,即寫(xiě)操作對(duì)其他讀線程即時(shí)可見(jiàn)。
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}ConcurrentHashMap構(gòu)造器
//initialCapacity:初始容量
//loadFactor:負(fù)載因子
//concurrencyLevel:ConcurrentHashMap內(nèi)部的Segment的數(shù)量
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//若concurrencyLevel大于MAX_SEGMENTS,則concurrencyLevel=MAX_SEGMENTS
//保證最大并發(fā)不超過(guò)MAX_SEGMENTS(1<<16)
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
//求解concurrencyLevel與2的幾次方最近
//如concurrencyLevel=5 則5與2^3=8最近 則sshift=8 ssize=3
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
//segmentShift和segmentMask主要用于元素的hash
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
//ConcurrentHashMap初始容量不超過(guò)MAXIMUM_CAPACITY(1<<30)
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//根據(jù)ConcurrentHashMap總?cè)萘縤nitialCapacity除以
//Segment[]數(shù)組的長(zhǎng)度得到單個(gè)分段鎖segment中HashEntry[]的大小
int c = initialCapacity / ssize;
//保證分段鎖segment的總?cè)萘縞不小于初始的容量
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
//cap為每個(gè)segment的初始容量,其值為離c天花板方向最近的2^n
//例:c為5 cap為8 c為12 cap為16
while (cap < c)
cap <<= 1;
// 創(chuàng)建Segment
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;
}ConcurrentHashMap put()源碼分析
put()方法向ConcurrentHashMap中添加元素
public V put(K key, V value) {
Segment<K,V> s;
//value不能為空
if (value == null)
throw new NullPointerException();
//計(jì)算key的hash值
int hash = hash(key);
//無(wú)符號(hào)右移segmentShift(默認(rèn)16)位
//然后& segmentMask(默認(rèn)15)得到segment在內(nèi)存中的位置
int j = (hash >>> segmentShift) & segmentMask;
如果Segment不存在,則調(diào)用ensureSegment方法
if ((s = (Segment<K,V>)UNSAFE.getObject
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
//初始化segment
s = ensureSegment(j);
//放值
return s.put(key, hash, value, false);
}Segment put()方法源碼解析
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// 嘗試直接獲取鎖,獲取到鎖node為null,
//否則調(diào)用scanAndLockForPut方法
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
// 獲取在tab數(shù)組中的位置
int index = (tab.length - 1) & hash;
// 得到鏈表的頭節(jié)點(diǎn)
HashEntry<K,V> first = entryAt(tab, index);
// 遍歷鏈表
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
// 遍歷到鏈表尾部,沒(méi)有重復(fù)的key,則新插入
else {
if (node != null)
// 頭插法,將node節(jié)點(diǎn)設(shè)為鏈表頭節(jié)點(diǎn)
node.setNext(first);
else
// 為null,則新建一個(gè)節(jié)點(diǎn)
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
// 若c超過(guò)閾值則擴(kuò)容,并且數(shù)組長(zhǎng)度小于MAXIMUM_CAPACITY = 1 << 30
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
// 擴(kuò)容并進(jìn)行重新hash
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}scanAndLockForPut
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
// 獲取鏈表頭結(jié)點(diǎn)
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
int retries = -1; // negative while locating node
// 不斷嘗試獲取鎖
while (!tryLock()) {
HashEntry<K,V> f; // to recheck first below
if (retries < 0) {
// 鏈表的頭結(jié)點(diǎn)為null,或者遍歷到鏈表的尾部
if (e == null) {
// 這里加條件是因?yàn)?,有可能已?jīng)初始化node節(jié)點(diǎn)了
// 結(jié)果由于頭結(jié)點(diǎn)改變重新遍歷鏈表
if (node == null) // speculatively create node
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
// 找到相同key的節(jié)點(diǎn)
else if (key.equals(e.key))
retries = 0;
// 沒(méi)有找到key對(duì)應(yīng)的節(jié)點(diǎn),指向下一個(gè)節(jié)點(diǎn)
else
e = e.next;
}
// 可用處理器數(shù)量大于1,MAX_SCAN_RETRIES=64,否則為1
else if (++retries > MAX_SCAN_RETRIES) {
// 調(diào)用ReentrantLock中NonfairSync的lock()方法
// 執(zhí)行過(guò)程中有可能不阻塞獲取到鎖,也有可能被阻塞
// 而不是之前的一直嘗試直接獲取鎖
lock();
break;
}
// 鏈表的頭結(jié)點(diǎn)發(fā)生變化,更新頭結(jié)點(diǎn),并重置retries值為-1
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
return node;
}到此這篇關(guān)于JDK1.7的ConcurrentHashMap源碼解析的文章就介紹到這了,更多相關(guān)ConcurrentHashMap源碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Security UserDetails實(shí)現(xiàn)原理詳解
這篇文章主要介紹了Spring Security UserDetails實(shí)現(xiàn)原理詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
IDEA 中 30 秒創(chuàng)建一個(gè) Spring Cloud Alibaba 工程
這篇文章主要介紹了IDEA 中 30 秒生成 Spring Cloud Alibaba 工程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì)對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
利用Netty+SpringBoot實(shí)現(xiàn)定時(shí)后端向前端推送數(shù)據(jù)
這篇文章主要介紹了BIO、NIO、AIO三種Java?IO模型,并探討了如何使用Spring?Boot集成Netty實(shí)現(xiàn)后臺(tái)向前端推送信息的功能,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-01-01
SpringBoot修改子模塊Module的jdk版本的方法 附修改原因
這篇文章主要介紹了SpringBoot修改子模塊Module的jdk版本的方法 附修改原因,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
SpringBoot項(xiàng)目啟動(dòng)數(shù)據(jù)加載內(nèi)存的三種方法
一般來(lái)說(shuō),SpringBoot工程環(huán)境配置放在properties文件中,啟動(dòng)的時(shí)候?qū)⒐こ讨械膒roperties/yaml文件的配置項(xiàng)加載到內(nèi)存中,本文給大家介紹了SpringBoot項(xiàng)目啟動(dòng)數(shù)據(jù)加載內(nèi)存中的三種方法,需要的朋友可以參考下2024-04-04
SpringBoot實(shí)現(xiàn)單點(diǎn)登錄的實(shí)現(xiàn)詳解
在現(xiàn)代的Web應(yīng)用程序中,單點(diǎn)登錄(Single?Sign-On)已經(jīng)變得越來(lái)越流行,在本文中,我們將使用Spring?Boot構(gòu)建一個(gè)基本的單點(diǎn)登錄系統(tǒng),需要的可以參考一下2023-05-05
詳解Java如何優(yōu)雅地書(shū)寫(xiě)if-else
在日常開(kāi)發(fā)中我們常常遇到有多個(gè)if?else的情況,之間書(shū)寫(xiě)顯得代碼冗余難看,對(duì)于追求更高質(zhì)量代碼的同學(xué),就會(huì)思考如何優(yōu)雅地處理這種代碼。本文我們就來(lái)探討下幾種優(yōu)化if?else的方法2022-08-08

