volatile是用來修飾被不同線程訪問和修改的變量表蝙,編譯器不會去假設(shè)這個變量的值粹湃。是輕量級的synchronized保證了共享變量的可見性蕴侧,也就是當(dāng)一個線程修改一個共享變量的時候,另外的線程能夠讀到這個修改的值赁濒,它不會引起線程上下文切換和調(diào)度轨奄。
如果一個字段被申明為volatile,java線程內(nèi)存模型確保所有線程看到這個變量的值是一致的拒炎。
為了提高運行速度處理器不直接與內(nèi)存進(jìn)行通信
“原子”代表最小的單位挪拟,所以原子操作可以看做最小的執(zhí)行單位,該操作在執(zhí)行完畢前不會被任何其他任務(wù)或事件打斷
處理器如何實現(xiàn)原子操作
32位 IA-32處理器基于對緩存加鎖或總線加鎖的方式來實現(xiàn)多處理器之間的原子操作击你。 首先處理器會自動保證基本的內(nèi)存操作的原子性玉组,保證從系統(tǒng)內(nèi)存中讀取或者寫入的一個字節(jié)是原子的,意思是當(dāng)一個處理器讀取一個字節(jié)時丁侄,其他處理器不能訪問這個字節(jié)的內(nèi)存地址惯雳。但是復(fù)雜的內(nèi)存操作處理器是不能自動保證其原子性的,比如跨總線寬度鸿摇、跨多個緩存行的石景、和跨頁表的訪問,但是可以通過 總線鎖定 和 緩存鎖定兩個機制來保存復(fù)雜內(nèi)存操作的原子性
使用總線鎖定 保證原子性
比如 多個處理器同時對共享變量進(jìn)行讀改寫操作 多個處理器同時進(jìn)行 讀改寫操作就不是原子的 比如進(jìn)行兩次i++操作 cpu1 執(zhí)行i=i+1 cpu2 也從它的緩存中讀取i進(jìn)行+1操作 然后分別寫入系統(tǒng)內(nèi)存 這個時候 i的值是1 與期望值2 不同拙吉。
解決這個問題的方法就是 保證CPU1讀改寫共享變量的時候潮孽,cpu2不能操作緩存了該共享變量內(nèi)存地址的緩存。也就是使用總線鎖筷黔,使得cpu1 獨占共享內(nèi)存往史。-
使用緩存鎖保證原子性
總線鎖 把cpu和內(nèi)存之間的通信鎖住,其他處理器不能操作其他內(nèi)存地址的數(shù)據(jù)佛舱,所以總線鎖的開銷比較大椎例,于是就出現(xiàn)了緩存鎖定。緩存鎖定就是 內(nèi)存區(qū)域如果被緩存在處理器的緩存行中名眉,并且在Lock操作期間被鎖定粟矿,那么當(dāng)它執(zhí)行鎖操作回寫到內(nèi)存的時候,不會鎖住總線损拢,而是修改處理器內(nèi)部的內(nèi)存地址并允許它的緩存一致性來保證操作的原子性陌粹,因為緩存一致性機制會阻止同時修改由兩個以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù),當(dāng)其他處理器回寫已被鎖定的緩存行數(shù)據(jù)時福压,會使得緩存無效掏秩。有兩種情況處理器不會使用緩存鎖定
1.操作的數(shù)據(jù)不能被緩存在處理其內(nèi)部或舞,或跨多個緩存行,會嗲用總線鎖定- 處理器不支持緩存鎖定蒙幻。
java實現(xiàn)原子操作
-
通過鎖和循環(huán)CAS(比較并交換)方式實現(xiàn)原子操作
JVM的CAS操作正是利用了處理器提供的CMPXCHG指令實現(xiàn)的映凳。JDK的并發(fā)包提供了一些類來支持原子操作 如 AtomicBoolean AtomicInteger AtomicLong 。
-
CAS實現(xiàn)原子操作的三大問題
- ABA問題 CAS需要在操作值得時候檢查值有沒有發(fā)生變化 如果 A -> B B -> A 則表面上是沒有變化實際變化了,ABA的解決思路是使用版本號邮破。在變量前追加版本號A B A 就會變成 1A 2B 3A JDK的Atomic包里面提供了一個類 AtomicStampedReference來解決ABA問題诈豌。這個類里面的CAS方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用和當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志。
- 循環(huán)時間長 開銷大
- 只能保證一個共享變量的原子操作抒和,多個共享變量的事后可以用鎖矫渔,或者把多個共享變量合并成一個共享變量來操作 比如 i=2 j=a 合并之后 ij=2a然后進(jìn)行CAS操作
3.使用鎖機制來實現(xiàn)原子操作
只有獲得鎖的線程才能夠操作鎖定的內(nèi)存區(qū)域,JVM內(nèi)部實現(xiàn)了 偏向鎖摧莽、輕量級鎖庙洼、互斥鎖 除了偏向鎖 其他方式都用了循環(huán)CAS