java ConcurrentHashMap分段加鎖提高并發(fā)效率
ConcurrentHashMap詳解
JDK7
Segment
在jdk8之前concurrentHashMap使用該對象進(jìn)行分段加鎖,降低了鎖的粒度,使得并發(fā)效率提高,Segment本身也相當(dāng)于一個(gè)HashMap,Segment包含一個(gè)HashEntry數(shù)組,數(shù)組中每個(gè)HashEntry既是一個(gè)鍵值對,又是一個(gè)鏈表的頭結(jié)點(diǎn)
get方法
- 根據(jù)key做hash運(yùn)算,得到hash值
- 通過hash值,定位到對應(yīng)的segment對象
- 再次通過hash值,定位到segment當(dāng)中數(shù)組的具體位置
put方法
- 根據(jù)key做hash運(yùn)算,得到hash值
- 通過hash值,定位到對應(yīng)的segment對象
- 獲取可重入鎖
- 再次通過hash值,定位到segment當(dāng)中數(shù)組的具體位置
- 插入或覆蓋hashEntry對象
- 釋放鎖
但是使用這種方式實(shí)現(xiàn)需要進(jìn)行兩次hash操作,第一次hash操作找到對應(yīng)的segment,第二次hash操作定位到元素所在鏈表的頭部
JDK8
在jdk8的時(shí)候參考了HashMap的設(shè)計(jì),采用了數(shù)組+鏈表+紅黑樹的方式,內(nèi)部大量采用CAS操作,舍棄了分段鎖的思想
CAS
CAS是compare and swap的縮寫,即我們所說的比較交換,CAS屬于樂觀鎖。
CAS包含三個(gè)操作數(shù),---內(nèi)存中的值(V),預(yù)期原值(A),新值(B) 如果內(nèi)存中的值和A的值一樣,就可以將內(nèi)存中的值更新為B。CAS通過無限循環(huán)來獲取數(shù)據(jù),一直到V和A一致為止
樂觀鎖
樂觀鎖會(huì)很樂觀的認(rèn)為不會(huì)出現(xiàn)并發(fā)問題,所以采用無鎖的機(jī)制來進(jìn)行處理,比如通過給記錄加version來獲取數(shù)據(jù),性能比悲觀鎖要高
悲觀鎖
悲觀鎖會(huì)很悲觀的認(rèn)為肯定會(huì)出現(xiàn)并發(fā)問題,所以會(huì)將資源鎖住,該資源只能有一個(gè)線程進(jìn)行操作,只有前一個(gè)獲得鎖的線程釋放鎖之后,下一個(gè)線程才可以訪問
源碼分析
重要變量
// 表示整個(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,哈希表還沒有初始化,進(jìn)行初始化哈希表 if (tab == null || (n = tab.length) == 0) tab = initTable(); // 該索引位置為null,表示還沒有元素 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; // 對頭節(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) { // 鏈表的長度是否達(dá)到8 達(dá)到8轉(zhuǎn)為紅黑樹 if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); // oldVal不為null,表示只是對key的值進(jìn)行的修改,沒有添加元素,直接返回即可 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è)方法要是可以好好地利用起來,那就爽歪歪了
- 當(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分段加鎖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java設(shè)計(jì)模式之中介者模式的實(shí)現(xiàn)方式
Java中介者模式是一種行為型設(shè)計(jì)模式,它通過一個(gè)中介者對象來協(xié)調(diào)多個(gè)對象之間的交互,降低對象之間的耦合度,提高系統(tǒng)的可維護(hù)性和可擴(kuò)展性。本文將介紹該設(shè)計(jì)模式的原理、使用場景和實(shí)現(xiàn)方法2023-04-04基于Zookeeper實(shí)現(xiàn)分布式鎖詳解
Zookeeper是一個(gè)分布式的,開源的分布式應(yīng)用程序協(xié)調(diào)服務(wù),是Hadoop和hbase的重要組件。這篇文章主要介紹了通過Zookeeper實(shí)現(xiàn)分布式鎖,感興趣的朋友可以了解一下2021-12-12SpringMVC統(tǒng)一異常處理實(shí)例代碼
這篇文章主要介紹了SpringMVC統(tǒng)一異常處理實(shí)例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Spring boot 整合 Okhttp3 并封裝請求工具實(shí)例 詳解
OkHttp作為一款成熟、穩(wěn)定、易用的HTTP客戶端庫,擁有較高的性能和多樣化的功能,已被廣泛應(yīng)用于移動(dòng)應(yīng)用開發(fā)、Web服務(wù)端開發(fā)等領(lǐng)域,這篇文章主要介紹了Spring boot 整合 Okhttp3 并封裝請求工具,需要的朋友可以參考下2023-08-08Mybatis Criteria使用and和or進(jìn)行聯(lián)合條件查詢的操作方法
這篇文章主要介紹了Mybatis Criteria的and和or進(jìn)行聯(lián)合條件查詢的方法,本文通過例子給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-10-10