一、導(dǎo)論
這些天一直在看關(guān)于多線程和高并發(fā)的書籍,也對(duì)jdk中的并發(fā)措施了解了些許,看到concurrentHashMap的時(shí)候感覺知識(shí)點(diǎn)很亂,有必要寫篇博客整理記錄一下。
當(dāng)資源在多線程下共享時(shí)會(huì)產(chǎn)生一些邏輯問題,這個(gè)時(shí)候類或者方法會(huì)產(chǎn)生不符合正常邏輯的結(jié)果,則不是線程安全的??v觀jdk的版本更新,可以看到j(luò)dk的開發(fā)人員在高并發(fā)和多線程下了很大的功夫,盡可能的通過jdk原生API來給開發(fā)人員帶來最方便最輕松的高并發(fā)數(shù)據(jù)模型,甚至想完全為開發(fā)人員解決并發(fā)問題,可以看得出來jdk的開發(fā)人員確實(shí)很用心。但是在大量業(yè)務(wù)數(shù)據(jù)的邏輯代碼的情況下高并發(fā)還是不可避免,也不可能完全通過jdk原生的并發(fā)API去解決這些并發(fā)問題,開發(fā)人員不得不自己去空值在高并發(fā)環(huán)境下的數(shù)據(jù)高可用性和一致性。
前面說了jdk原生的API已經(jīng)有了很多的高并發(fā)產(chǎn)品,在java.util.concurrent包下有很多解決高并發(fā),高吞吐量,多線程問題的API。比如線程池ThreadPoolExecutor,線程池工廠Executors,F(xiàn)uture模式下的接口Future,阻塞隊(duì)列BlockingQueue等等。
二、正文
1、數(shù)據(jù)的可見性
直接進(jìn)入正題,concurrentHashMap相信用的人也很多,因?yàn)樵跀?shù)據(jù)安全性上確實(shí)比HashMap好用,在性能上比hashtable也好用。大家都知道線程在操作一個(gè)變量的時(shí)候,比如i++,jvm執(zhí)行的時(shí)候需要經(jīng)過兩個(gè)內(nèi)存,主內(nèi)存和工作內(nèi)存。那么在線程A對(duì)i進(jìn)行加1的時(shí)候,它需要去主內(nèi)存拿到變量值,這個(gè)時(shí)候工作內(nèi)存中便有了一個(gè)變量數(shù)據(jù)的副本,執(zhí)行完這些之后,再去對(duì)變量真正的加1,但是此時(shí)線程B也要操作變量,并且邏輯上也是沒有維護(hù)多線程訪問的限制,則很有可能在線程A在從主內(nèi)存獲取數(shù)據(jù)并在修改的時(shí)候線程B去主內(nèi)存拿數(shù)據(jù),但是這個(gè)時(shí)候主內(nèi)存的數(shù)據(jù)還沒有更新,A線程還沒有來得及講加1后的變量回填到主內(nèi)存,這個(gè)時(shí)候變量在這兩個(gè)線程操作的情況下就會(huì)發(fā)生邏輯錯(cuò)誤。
2、原子性
原子性就是當(dāng)某一個(gè)線程A修改i的值的時(shí)候,從取出i到將新的i的值寫給i之間線程B不能對(duì)i進(jìn)行任何操作。也就是說保證某個(gè)線程對(duì)i的操作是原子性的,這樣就可以避免數(shù)據(jù)臟讀。
3、volatile的作用
Volatile保證了數(shù)據(jù)在多線程之間的可見性,每個(gè)線程在獲取volatile修飾的變量時(shí)候都回去主內(nèi)存獲取,所以當(dāng)線程A修改了被volatile修飾的數(shù)據(jù)后其他線程看到的一定是修改過后最新的數(shù)據(jù),也是因?yàn)関olatile修飾的變量數(shù)據(jù)每次都要去主內(nèi)存獲取,在性能上會(huì)有些犧牲。
4、措施
HashMap在多線程的場(chǎng)景下是不安全的,hashtable雖然是在數(shù)據(jù)表上加鎖,縱然數(shù)據(jù)安全了,但是性能方面確實(shí)不如HashMap。那么來看看concurrentHashMap是如何解決這些問題的。
concurrentHashMap由多個(gè)segment組成,每一個(gè)segment都包含了一個(gè)HashEntry數(shù)組的hashtable, 每一個(gè)segment包含了對(duì)自己的hashtable的操作,比如get,put,replace等操作(這些操作與HashMap邏輯都是一樣的,不同的是concurrentHashMap在執(zhí)行這些操作的時(shí)候加入了重入鎖ReentrantLock),這些操作發(fā)生的時(shí)候,對(duì)自己的hashtable進(jìn)行鎖定。由于每一個(gè)segment寫操作只鎖定自己的hashtable,所以可能存在多個(gè)線程同時(shí)寫的情況,性能無疑好于只有一個(gè)hashtable鎖定的情況。通俗的講就是concurrentHashMap由多個(gè)hashtable組成。
5、源碼
看下concurrentHashMap的remove操作
V remove(Object key, int hash, Object value) { lock();//重入鎖 try { int c = count - 1; HashEntry<K,V>[] tab = table; int index = hash & (tab.length - 1); HashEntry<K,V> first = tab[index]; HashEntry<K,V> e = first; while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue = null; if (e != null) { V v = e.value; if (value == null || value.equals(v)) { oldValue = v; // All entries following removed node can stay // in list, but all preceding ones need to be // cloned. ++modCount; HashEntry<K,V> newFirst = e.next; for (HashEntry<K,V> p = first; p != e; p = p.next) newFirst = new HashEntry<K,V>(p.key, p.hash, newFirst, p.value); tab[index] = newFirst; count = c; // write-volatile } } return oldValue; } finally { unlock();//釋放鎖 } }
Count是被volatile所修飾,保證了count的可見性,避免操作數(shù)據(jù)的時(shí)候產(chǎn)生邏輯錯(cuò)誤。segment中的remove操作和HashMap大致一樣,HashMap沒有l(wèi)ock()和unlock()操作。
看下concurrentHashMap的get源碼
V get(Object key, int hash) { if (count != 0) { // read-volatile
HashEntry<K,V> e = getFirst(hash);
//如果沒有找到則直接返回null
while (e != null) { if (e.hash == hash && key.equals(e.key)) {
//由于沒有加鎖,在get的過程中,可能會(huì)有更新,拿到的key對(duì)應(yīng)的value可能為null,需要單獨(dú)判斷一遍
V v = e.value;
//如果value為不為null,則返回獲取到的value
if (v != null) return v; return readValueUnderLock(e); // recheck }
e = e.next;
}
} return null;
}
關(guān)于concurrentHashMap的get的相關(guān)說明已經(jīng)在上面代碼中給出了注釋,這里就不多說了。
看下concurrentHashMap中的put
public V put(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value, false); }
可以看到concurrentHashMap不允許key或者value為null
接下來看下segment的put
V put(K key, int hash, V value, boolean onlyIfAbsent) { lock(); try { int c = count; if (c++ > threshold) // ensure capacity rehash(); HashEntry<K,V>[] tab = table; int index = hash & (tab.length - 1); HashEntry<K,V> first = tab[index]; HashEntry<K,V> e = first; while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue; if (e != null) { oldValue = e.value; if (!onlyIfAbsent) e.value = value; } else { oldValue = null; ++modCount; tab[index] = new HashEntry<K,V>(key, hash, first, value); count = c; // write-volatile } return oldValue; } finally { unlock(); } }
同樣也是加入了重入鎖,其他的基本和HashMap邏輯差不多。值得一提的是jdk8中添加的中的putval,這里就不多說了。
三、總結(jié)
ConcurrentHashmap將數(shù)據(jù)結(jié)構(gòu)分為了多個(gè)Segment,也是使用重入鎖來解決高并發(fā),講他分為多個(gè)segment是為了減小鎖的力度,添加的時(shí)候加了鎖,索引的時(shí)候沒有加鎖,使用volatile修飾count是為了保持count的可見性,都是jdk為了解決并發(fā)和多線程操作的常用手段。
作者:當(dāng)代唐寅 只是向上走,不必聽自暴自棄者流的話。能做事的做事,能發(fā)聲的發(fā)聲。有一分熱,發(fā)一分光,就令螢火一般,也可以在黑暗里發(fā)一點(diǎn)光,不必等候炬火。此后如竟沒有炬火,我便是唯一的光。 ——魯迅《熱風(fēng)》
出處:http://www.cnblogs.com/mxlandxt/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載。
http://www.cnblogs.com/mxlandxt/p/7122723.html