java ConcurrentHashMap分段加鎖提高并發(fā)效率
ConcurrentHashMap詳解
JDK7
Segment
在jdk8之前concurrentHashMap使用該對(duì)象進(jìn)行分段加鎖,降低了鎖的粒度,使得并發(fā)效率提高,Segment本身也相當(dāng)于一個(gè)HashMap,Segment包含一個(gè)HashEntry數(shù)組,數(shù)組中每個(gè)HashEntry既是一個(gè)鍵值對(duì),又是一個(gè)鏈表的頭結(jié)點(diǎn)
get方法
- 根據(jù)key做hash運(yùn)算,得到hash值
- 通過(guò)hash值,定位到對(duì)應(yīng)的segment對(duì)象
- 再次通過(guò)hash值,定位到segment當(dāng)中數(shù)組的具體位置
put方法
- 根據(jù)key做hash運(yùn)算,得到hash值
- 通過(guò)hash值,定位到對(duì)應(yīng)的segment對(duì)象
- 獲取可重入鎖
- 再次通過(guò)hash值,定位到segment當(dāng)中數(shù)組的具體位置
- 插入或覆蓋hashEntry對(duì)象
- 釋放鎖
但是使用這種方式實(shí)現(xiàn)需要進(jìn)行兩次hash操作,第一次hash操作找到對(duì)應(yīng)的segment,第二次hash操作定位到元素所在鏈表的頭部
JDK8
在jdk8的時(shí)候參考了HashMap的設(shè)計(jì),采用了數(shù)組+鏈表+紅黑樹的方式,內(nèi)部大量采用CAS操作,舍棄了分段鎖的思想
CAS
CAS是compare and swap的縮寫,即我們所說(shuō)的比較交換,CAS屬于樂(lè)觀鎖。
CAS包含三個(gè)操作數(shù),---內(nèi)存中的值(V),預(yù)期原值(A),新值(B) 如果內(nèi)存中的值和A的值一樣,就可以將內(nèi)存中的值更新為B。CAS通過(guò)無(wú)限循環(huán)來(lái)獲取數(shù)據(jù),一直到V和A一致為止
樂(lè)觀鎖
樂(lè)觀鎖會(huì)很樂(lè)觀的認(rèn)為不會(huì)出現(xiàn)并發(fā)問(wèn)題,所以采用無(wú)鎖的機(jī)制來(lái)進(jìn)行處理,比如通過(guò)給記錄加version來(lái)獲取數(shù)據(jù),性能比悲觀鎖要高
悲觀鎖
悲觀鎖會(huì)很悲觀的認(rèn)為肯定會(huì)出現(xiàn)并發(fā)問(wèn)題,所以會(huì)將資源鎖住,該資源只能有一個(gè)線程進(jìn)行操作,只有前一個(gè)獲得鎖的線程釋放鎖之后,下一個(gè)線程才可以訪問(wèn)
源碼分析
重要變量
// 表示整個(gè)hash表,初始化階段是在第一次插入的時(shí)候,容量總是2的次冪 transient volatile Node<K,V>[] table; // 下一個(gè)使用的表 只有在擴(kuò)容的時(shí)候非空,其他情況都是null private transient volatile Node<K,V>[] nextTable; /** * Base counter value, used mainly when there is no contention, * but also as a fallback during table initialization * races. Updated via CAS. */ private transient volatile long baseCount; // 用于初始化和擴(kuò)容控制 // 0:默認(rèn)值 // -1:正在初始化 // 大于0:為hash表的閾值 // 小于-1:有多個(gè)線程在進(jìn)行擴(kuò)容 該值為 -(1+正在擴(kuò)容的線程數(shù)) private transient volatile int sizeCtl; /** * The next table index (plus one) to split while resizing. */ private transient volatile int transferIndex; /** * Spinlock (locked via CAS) used when resizing and/or creating CounterCells. */ private transient volatile int cellsBusy; /** * Table of counter cells. When non-null, size is a power of 2. */ private transient volatile CounterCell[] counterCells; // views private transient KeySetView<K,V> keySet; private transient ValuesView<K,V> values; private transient EntrySetView<K,V> entrySet;
構(gòu)造函數(shù)
/**
* Creates a new, empty map with the default initial table size (16).
*/
public ConcurrentHashMap() {
}
/**
* Creates a new, empty map with an initial table size
* accommodating the specified number of elements without the need
* to dynamically resize.
*
* @param initialCapacity The implementation performs internal
* sizing to accommodate this many elements.
* @throws IllegalArgumentException if the initial capacity of
* elements is negative
*/
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
/**
* Creates a new map with the same mappings as the given map.
*
* @param m the map
*/
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
this.sizeCtl = DEFAULT_CAPACITY;
putAll(m);
}
/**
* Creates a new, empty map with an initial table size based on
* the given number of elements ({@code initialCapacity}) and
* initial table density ({@code loadFactor}).
*
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements,
* given the specified load factor.
* @param loadFactor the load factor (table density) for
* establishing the initial table size
* @throws IllegalArgumentException if the initial capacity of
* elements is negative or the load factor is nonpositive
*
* @since 1.6
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
/**
* Creates a new, empty map with an initial table size based on
* the given number of elements ({@code initialCapacity}), table
* density ({@code loadFactor}), and number of concurrently
* updating threads ({@code concurrencyLevel}).
*
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements,
* given the specified load factor.
* @param loadFactor the load factor (table density) for
* establishing the initial table size
* @param concurrencyLevel the estimated number of concurrently
* updating threads. The implementation may use this value as
* a sizing hint.
* @throws IllegalArgumentException if the initial capacity is
* negative or the load factor or concurrencyLevel are
* nonpositive
*/
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 bins
initialCapacity = concurrencyLevel; // as estimated threads
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
}重要方法
put方法
ConcurrentHashMap是如何保證在插入的時(shí)候線程安全的呢
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();
// 計(jì)算hash值
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// tab為null,哈希表還沒(méi)有初始化,進(jìn)行初始化哈希表
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 該索引位置為null,表示還沒(méi)有元素
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 使用CAS的方式添加節(jié)點(diǎn)
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 節(jié)點(diǎn)的hash值為-1,表示該哈希表正在擴(kuò)容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
// 對(duì)頭節(jié)點(diǎn)加鎖
synchronized (f) {
// 再次判斷一下該節(jié)點(diǎn)是否為目標(biāo)索引位置的頭節(jié)點(diǎn),防止期間被修改
if (tabAt(tab, i) == f) {
// 表示是普通的鏈表
if (fh >= 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)))) {
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;
}
}
}
// 紅黑樹 TreeBin的hash值為TREEBIN,是-2
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;
}
}
}
}
// 可以看一下上述的賦值流程
// 默認(rèn)初始值是0
// 鏈表時(shí)為1 在遍歷時(shí)進(jìn)行累加,直到找到所要添加的位置為止
// 紅黑樹時(shí)為2
if (binCount != 0) {
// 鏈表的長(zhǎng)度是否達(dá)到8 達(dá)到8轉(zhuǎn)為紅黑樹
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
// oldVal不為null,表示只是對(duì)key的值進(jìn)行的修改,沒(méi)有添加元素,直接返回即可
if (oldVal != null)
return oldVal;
break;
}
}
}
//
addCount(1L, binCount);
return null;
}哈希函數(shù)根據(jù)hashCode計(jì)算出哈希值,這里的hash值與HashMap的計(jì)算方式稍微有點(diǎn)不同,在低十六位異或高十六位之后還需要與HASH_BITS在進(jìn)行與運(yùn)算,HASH_BITS的值是0x7fffffff,轉(zhuǎn)為二進(jìn)制是31個(gè)1,進(jìn)行與運(yùn)算是為了保證得到的hash值為正數(shù)。
ConcurrentHashMap中hash值為負(fù)數(shù)包含有其他含義,-1表示為ForwardingNode節(jié)點(diǎn),-2表示為TreeBin節(jié)點(diǎn)
static final int spread(int h) {
// (h ^ (h >>> 16)與hashMap相同
// HASH_BITS進(jìn)行與運(yùn)算
return (h ^ (h >>> 16)) & HASH_BITS;
}初始化hash表的操作
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
// hash表為null時(shí)才需要進(jìn)行初始化
while ((tab = table) == null || tab.length == 0) {
// sizeCtl小于0表示有其他線程在進(jìn)行初始化操作了
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// 將SIZECTL設(shè)為-1,表示該線程要開始初始化表了
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;
// n右移兩位 表示1/4n n-1/4n為3/4n 即為n*0.75
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}computeIfAbsent和putIfAbsent方法
ConcurrentHashMap有兩個(gè)比較特殊的方法,這兩個(gè)方法要是可以好好地利用起來(lái),那就爽歪歪了
- 當(dāng)Key存在的時(shí)候,如果Value獲取比較昂貴的話,putIfAbsent就白白浪費(fèi)時(shí)間在獲取這個(gè)昂貴的Value上(這個(gè)點(diǎn)特別注意)
- Key不存在的時(shí)候,putIfAbsent返回null,小心空指針,而computeIfAbsent返回計(jì)算后的值
- 當(dāng)Key不存在的時(shí)候,putIfAbsent允許put null進(jìn)去,而computeIfAbsent不能,之后進(jìn)行containsKey查詢是有區(qū)別的
以上就是java ConcurrentHashMap分段加鎖提高并發(fā)效率的詳細(xì)內(nèi)容,更多關(guān)于java ConcurrentHashMap分段加鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java設(shè)計(jì)模式之中介者模式的實(shí)現(xiàn)方式
Java中介者模式是一種行為型設(shè)計(jì)模式,它通過(guò)一個(gè)中介者對(duì)象來(lái)協(xié)調(diào)多個(gè)對(duì)象之間的交互,降低對(duì)象之間的耦合度,提高系統(tǒng)的可維護(hù)性和可擴(kuò)展性。本文將介紹該設(shè)計(jì)模式的原理、使用場(chǎng)景和實(shí)現(xiàn)方法2023-04-04
基于Zookeeper實(shí)現(xiàn)分布式鎖詳解
Zookeeper是一個(gè)分布式的,開源的分布式應(yīng)用程序協(xié)調(diào)服務(wù),是Hadoop和hbase的重要組件。這篇文章主要介紹了通過(guò)Zookeeper實(shí)現(xiàn)分布式鎖,感興趣的朋友可以了解一下2021-12-12
SpringMVC統(tǒng)一異常處理實(shí)例代碼
這篇文章主要介紹了SpringMVC統(tǒng)一異常處理實(shí)例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
Spring boot 整合 Okhttp3 并封裝請(qǐng)求工具實(shí)例 詳解
OkHttp作為一款成熟、穩(wěn)定、易用的HTTP客戶端庫(kù),擁有較高的性能和多樣化的功能,已被廣泛應(yīng)用于移動(dòng)應(yīng)用開發(fā)、Web服務(wù)端開發(fā)等領(lǐng)域,這篇文章主要介紹了Spring boot 整合 Okhttp3 并封裝請(qǐng)求工具,需要的朋友可以參考下2023-08-08
Mybatis Criteria使用and和or進(jìn)行聯(lián)合條件查詢的操作方法
這篇文章主要介紹了Mybatis Criteria的and和or進(jìn)行聯(lián)合條件查詢的方法,本文通過(guò)例子給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-10-10

