本系列文章只做個人讀書筆記
首先明確:Java中所使用的并發(fā)機(jī)制依賴于JVM的實現(xiàn)和CPU的指令。
1、volatile的分析:
????? volatile如何保證內(nèi)存可見性:
? ? ? ?volatile修飾的共享變量,轉(zhuǎn)成匯編代碼會多出一個Lock前綴的指令,Lock前綴的指令在多核處理器下會引發(fā)兩件事情:
? ?1)將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)中
? ?2)這個寫回內(nèi)存的操作會使在其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效
????????那么,問題來了,他是怎么使用其他CPU里緩存該內(nèi)存地址的數(shù)據(jù)無效的呢,這就要涉及到緩存一致性,即(MESI),什么是MESI, MESI是緩存的數(shù)據(jù)的四種狀態(tài),M-Modified、E-Exclusive、S-Shared、I-Invalid,了解更多?MESI詳解傳送門?
? ? 總結(jié)一點,其實這些狀態(tài)的變更,是各CPU通過在總線上嗅探來實現(xiàn)的。
? ? ? ? 在看到此章節(jié)時,有個小小的疑問,請大神些解惑:
? ? ? ? 文中提到Loug lea大神在JDK7的并發(fā)包里新增一個隊列集合類LinkedTransferQueue,在使用volatile變量時,用追加字節(jié)的方式來優(yōu)化出隊和入隊的性能,這樣可以把頭、尾節(jié)點讀入不同的緩存行,這樣操作,出隊,入隊互不影響,這點可理解,有一點說到在隊列頭、尾節(jié)點都不中64字節(jié)(64位架構(gòu)下,緩存行的最大字節(jié)數(shù))時,會將它們都讀到一個緩存行,我的疑問是:
? ? 頭、尾節(jié)點的總字節(jié)數(shù)如果超過64字節(jié),這個時候應(yīng)該是怎么讀到緩存行呢?
2、synchronized的實現(xiàn)原理
????synchronized實現(xiàn)同步的基礎(chǔ):java中第一個對象都可以作鎖(怎么知道這個對象被被的線程持有呢-這個涉及到對象頭的MarkWord接下來會介紹)。
? ? ?synchronized可表現(xiàn)為以下三種形式
1、修飾普通同步方法 鎖的是當(dāng)前實例對象
2、修飾靜態(tài)同步方式 鎖的是當(dāng)前類對象
3、修飾代碼塊 鎖的是synchronized括號里的對象
? ? ? ? 從JVM規(guī)范中可以看到Synchonized在JVM里的實現(xiàn)原理,JVM基于進(jìn)入和退出Monitor對象來實現(xiàn)方法同步和代碼塊同步,但兩者的實現(xiàn)細(xì)節(jié)不一樣。代碼塊同步是使用monitorenter和monitorexit指令實現(xiàn)的,而方法同步是使用另外一種方式實現(xiàn)的,細(xì)節(jié)在JVM規(guī)范里并沒有詳細(xì)說明。但是,方法的同步同樣可以使用這兩個指令來實現(xiàn)。
?對象頭
????????synchronized用的鎖是存在Java對象頭里的。如果對象是數(shù)組類型,則虛擬機(jī)用3個字寬(Word)存儲對象頭,如果對象是非數(shù)組類型,則用2字寬存儲對象頭

Java對象頭里的Mark Word里默認(rèn)存儲對象的HashCode、分代年齡和鎖標(biāo)記位。32位JVM的Mark Word的默認(rèn)存儲結(jié)構(gòu)如下


64位架構(gòu)下其存儲結(jié)構(gòu):

鎖的種類:
????????鎖一共有4種狀態(tài),級別從低到高依次是:無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級鎖狀態(tài)和重量級鎖狀態(tài),這幾個狀態(tài)會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率
鎖的優(yōu)缺點:

3、原子操作
原子(atomic)本意是“不能被進(jìn)一步分割的最小粒子”,而原子操作(atomic operation)意為“不可被中斷的一個或一系列操作”
處理器如何實現(xiàn)原子操作
? ? ? ? ? 32位IA-32處理器使用基于對緩存加鎖或總線加鎖的方式來實現(xiàn)多處理器之間的原子操作。首先處理器會自動保證基本的內(nèi)存操作的原子性。處理器保證從系統(tǒng)內(nèi)存中讀取或者寫入一個字節(jié)是原子的,意思是當(dāng)一個處理器讀取一個字節(jié)時,其他處理器不能訪問這個字節(jié)的內(nèi)存地址。Pentium 6和最新的處理器能自動保證單處理器對同一個緩存行里進(jìn)行16/32/64位的操作是原子的,但是復(fù)雜的內(nèi)存操作處理器是不能自動保證其原子性的,比如跨總線寬度、跨多個緩存行和跨頁表的訪問。但是,處理器提供總線鎖定和緩存鎖定兩個機(jī)制來保證復(fù)雜內(nèi)存操作的原子性
情況下處理器不會使用緩存鎖定
第一種情況是:當(dāng)操作的數(shù)據(jù)不能被緩存在處理器內(nèi)部,或操作的數(shù)據(jù)跨多個緩存行(cache line)時,則處理器會調(diào)用總線鎖定。
第二種情況是:有些處理器不支持緩存鎖定。對于Intel 486和Pentium處理器,就算鎖定的內(nèi)存區(qū)域在處理器的緩存行中也會調(diào)用總線鎖定。
4、Java如何實現(xiàn)原子操作
? ? ?1)使用循環(huán)CAS實現(xiàn)原子操作
? ? ? ? ? ?CAS三大問題:
? ? ? ? ? ? a) ABS問題 解決方案加版本號
? ? ? ? ? ? b) 循環(huán)時間長開銷大
? ? ? ? ? ? c) 只能保證一個共享變量的原子操作 ,如果是多個,可以采用合并成一個共享變量來操作。
? ? ?2)使用鎖機(jī)制實現(xiàn)原子操作?
????????????鎖機(jī)制保證了只有獲得鎖的線程才能夠操作鎖定的內(nèi)存區(qū)域。JVM內(nèi)部實現(xiàn)了很多種鎖機(jī)制,有偏向鎖、輕量級鎖和互斥鎖。有意思的是除了偏向鎖,JVM實現(xiàn)鎖的方式都用了循環(huán)CAS,即當(dāng)一個線程想進(jìn)入同步塊的時候使用循環(huán)CAS的方式來獲取鎖,當(dāng)它退出同步塊的時候使用循環(huán)CAS釋放鎖。