一、導(dǎo)論
這些天一直在看關(guān)于多線程和高并發(fā)的書(shū)籍,也對(duì)jdk中的并發(fā)措施了解了些許,看到concurrentHashMap的時(shí)候感覺(jué)知識(shí)點(diǎn)很亂,有必要寫(xiě)篇博客整理記錄一下。
當(dāng)資源在多線程下共享時(shí)會(huì)產(chǎn)生一些邏輯問(wèn)題,這個(gè)時(shí)候類或者方法會(huì)產(chǎn)生不符合正常邏輯的結(jié)果,則不是線程安全的??v觀jdk的版本更新,可以看到j(luò)dk的開(kāi)發(fā)人員在高并發(fā)和多線程下了很大的功夫,盡可能的通過(guò)jdk原生API來(lái)給開(kāi)發(fā)人員帶來(lái)最方便最輕松的高并發(fā)數(shù)據(jù)模型,甚至想完全為開(kāi)發(fā)人員解決并發(fā)問(wèn)題,可以看得出來(lái)jdk的開(kāi)發(fā)人員確實(shí)很用心。但是在大量業(yè)務(wù)數(shù)據(jù)的邏輯代碼的情況下高并發(fā)還是不可避免,也不可能完全通過(guò)jdk原生的并發(fā)API去解決這些并發(fā)問(wèn)題,開(kāi)發(fā)人員不得不自己去空值在高并發(fā)環(huán)境下的數(shù)據(jù)高可用性和一致性。
前面說(shuō)了jdk原生的API已經(jīng)有了很多的高并發(fā)產(chǎn)品,在java.util.concurrent包下有很多解決高并發(fā),高吞吐量,多線程問(wèn)題的API。比如線程池ThreadPoolExecutor,線程池工廠Executors,F(xiàn)uture模式下的接口Future,阻塞隊(duì)列BlockingQueue等等。
二、正文
1、數(shù)據(jù)的可見(jiàn)性
直接進(jìn)入正題,concurrentHashMap相信用的人也很多,因?yàn)樵跀?shù)據(jù)安全性上確實(shí)比HashMap好用,在性能上比hashtable也好用。大家都知道線程在操作一個(gè)變量的時(shí)候,比如i++,jvm執(zhí)行的時(shí)候需要經(jīng)過(guò)兩個(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也要操作變量,并且邏輯上也是沒(méi)有維護(hù)多線程訪問(wèn)的限制,則很有可能在線程A在從主內(nèi)存獲取數(shù)據(jù)并在修改的時(shí)候線程B去主內(nèi)存拿數(shù)據(jù),但是這個(gè)時(shí)候主內(nèi)存的數(shù)據(jù)還沒(méi)有更新,A線程還沒(méi)有來(lái)得及講加1后的變量回填到主內(nèi)存,這個(gè)時(shí)候變量在這兩個(gè)線程操作的情況下就會(huì)發(fā)生邏輯錯(cuò)誤。
2、原子性
原子性就是當(dāng)某一個(gè)線程A修改i的值的時(shí)候,從取出i到將新的i的值寫(xiě)給i之間線程B不能對(duì)i進(jìn)行任何操作。也就是說(shuō)保證某個(gè)線程對(duì)i的操作是原子性的,這樣就可以避免數(shù)據(jù)臟讀。
3、volatile的作用
Volatile保證了數(shù)據(jù)在多線程之間的可見(jiàn)性,每個(gè)線程在獲取volatile修飾的變量時(shí)候都回去主內(nèi)存獲取,所以當(dāng)線程A修改了被volatile修飾的數(shù)據(jù)后其他線程看到的一定是修改過(guò)后最新的數(shù)據(jù),也是因?yàn)関olatile修飾的變量數(shù)據(jù)每次都要去主內(nèi)存獲取,在性能上會(huì)有些犧牲。
4、措施
HashMap在多線程的場(chǎng)景下是不安全的,hashtable雖然是在數(shù)據(jù)表上加鎖,縱然數(shù)據(jù)安全了,但是性能方面確