volatile原理与技巧
轉(zhuǎn)自http://www.iteye.com/topic/109150
volatile, 用更低的代價(jià)替代同步
為什么 使用volatile比同步代價(jià)更低??
同步的代價(jià), 主要由其覆蓋范圍決定, 如果可以降低同步的覆蓋范圍, 則可以大幅提升程序性能.
而volatile的覆蓋范圍僅僅變量級(jí)別的. 因此它的同步代價(jià)很低.
?
volatile原理是什么?
volatile的語(yǔ)義, 其實(shí)是告訴處理器, 不要將我放入工作內(nèi)存, 請(qǐng)直接在主存操作我.(工作內(nèi)存詳見java內(nèi)存模型)
因此, 當(dāng)多核或多線程在訪問(wèn)該變量時(shí), 都將直接 操作 主存, 這從本質(zhì)上, 做到了變量共享.
?
volatile的有什么優(yōu)勢(shì)??
1, 更大的程序吞吐量
2, 更少的代碼實(shí)現(xiàn)多線程
3, 程序的伸縮性較好
4, 比較好理解, 無(wú)需太高的學(xué)習(xí)成本
?
volatile有什么劣勢(shì)??
1, 容易出問(wèn)題
2, 比較難設(shè)計(jì)
?
volatile運(yùn)算存在臟數(shù)據(jù)問(wèn)題
volatile僅僅能保證變量可見性, 無(wú)法保證原子性.
?
volatile的race condition示例:
Java代碼???
當(dāng)多線程執(zhí)行increase方法時(shí), 是否能保證它的值會(huì)是線性遞增的呢??
答案是否定的.
?
原因:
這里的increase方法, 執(zhí)行的操作是i++, 即 i = i + 1;
針對(duì)i = i + 1, 在多線程中的運(yùn)算, 本身需要改變i的值.
如果, 在i已從內(nèi)存中取到最新值, 但未與1進(jìn)行運(yùn)算, 此時(shí)其他線程已數(shù)次將運(yùn)算結(jié)果賦值給i.
則當(dāng)前線程結(jié)束時(shí), 之前的數(shù)次運(yùn)算結(jié)果都將被覆蓋.
即, 執(zhí)行100次increase, 可能結(jié)果是 < 100.
一般來(lái)說(shuō), 這種情況需要較高的壓力與并發(fā)情況下, 才會(huì)出現(xiàn).
?
如何避免這種情況??
解決以上問(wèn)題的方法:
一種是 操作時(shí), 加上同步.
這種方法, 無(wú)疑將大大降低程序性能, 且違背了volatile的初衷.
?
第二種方式是, 使用硬件原語(yǔ)(CAS), 實(shí)現(xiàn)非阻塞算法
從CPU原語(yǔ)上, 支持變量級(jí)別的低開銷同步.
?
CPU原語(yǔ)-比較并交換(CompareAndSet),實(shí)現(xiàn)非阻塞算法
什么是CAS??
cas是現(xiàn)代CPU提供給并發(fā)程序使用的原語(yǔ)操作. 不同的CPU有不同的使用規(guī)范.
在 Intel 處理器中,比較并交換通過(guò)指令的 cmpxchg 系列實(shí)現(xiàn)。
PowerPC 處理器有一對(duì)名為“加載并保留”和“條件存儲(chǔ)”的指令,它們實(shí)現(xiàn)相同的目地;
MIPS 與 PowerPC 處理器相似,除了第一個(gè)指令稱為“加載鏈接”。
CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)
?
什么是非阻塞算法??
一個(gè)線程的失敗或掛起不應(yīng)該影響其他線程的失敗或掛起.這類算法稱之為非阻塞(nonblocking)算法
對(duì)比阻塞算法:
如果有一類并發(fā)操作, 其中一個(gè)線程優(yōu)先得到對(duì)象監(jiān)視器的鎖, 當(dāng)其他線程到達(dá)同步邊界時(shí), 就會(huì)被阻塞.
直到前一個(gè)線程釋放掉鎖后, 才可以繼續(xù)競(jìng)爭(zhēng)對(duì)象鎖.(當(dāng)然,這里的競(jìng)爭(zhēng)也可是公平的, 按先來(lái)后到的次序)
?
CAS 原理:
我認(rèn)為位置 V 應(yīng)該包含值 A;如果包含該值,則將 B 放到這個(gè)位置;否則,不要更改該位置,只告訴我這個(gè)位置現(xiàn)在的值即可。
?
CAS使用示例(jdk 1.5 并發(fā)包 AtomicInteger類分析:)
?
Java代碼???
這個(gè)方法是, AtomicInteger類的常用方法, 作用是, 將變量設(shè)置為指定值, 并返回設(shè)置前的值.
它利用了cpu原語(yǔ)compareAndSet來(lái)保障值的唯一性.
另, AtomicInteger類中, 其他的實(shí)用方法, 也是基于同樣的實(shí)現(xiàn)方式.
比如 getAndIncrement, getAndDecrement, getAndAdd等等.
?
CAS語(yǔ)義上存在的 " ABA 問(wèn)題"
什么是ABA問(wèn)題?
假設(shè), 第一次讀取V地址的A值, 然后通過(guò)CAS來(lái)判斷V地址的值是否仍舊為A, 如果是, 就將B的值寫入V地址,覆蓋A值.
但是, 語(yǔ)義上, 有一個(gè)漏洞, 當(dāng)?shù)谝淮巫x取V的A值, 此時(shí), 內(nèi)存V的值變?yōu)锽值, 然后在未執(zhí)行CAS前, 又變回了A值.
此時(shí), CAS再執(zhí)行時(shí), 會(huì)判斷其正確的, 并進(jìn)行賦值.
?
這種判斷值的方式來(lái)斷定內(nèi)存是否被修改過(guò), 針對(duì)某些問(wèn)題, 是不適用的.
為了解決這種問(wèn)題, jdk 1.5并發(fā)包提供了AtomicStampedReference(有標(biāo)記的原子引用)類, 通過(guò)控制變量值的版本來(lái)保證CAS正確性.
?
其實(shí), 大部分通過(guò)值的變化來(lái)CAS, 已經(jīng)夠用了.
?
jdk1.5原子包介紹(基于volatile)
包的特色:
1, 普通原子數(shù)值類型AtomicInteger, AtomicLong提供一些原子操作的加減運(yùn)算.
2, 使用了解決臟數(shù)據(jù)問(wèn)題的經(jīng)典模式-"比對(duì)后設(shè)定", 即 查看主存中數(shù)據(jù)是否與預(yù)期提供的值一致,如果一致,才更新.
3, 使用AtomicReference可以實(shí)現(xiàn)對(duì)所有對(duì)象的原子引用及賦值.包括Double與Float,
但不包括對(duì)其的計(jì)算.浮點(diǎn)的計(jì)算,只能依靠同步關(guān)鍵字或Lock接口來(lái)實(shí)現(xiàn)了.
4, 對(duì)數(shù)組元素里的對(duì)象,符合以上特點(diǎn)的, 也可采用原子操作.包里提供了一些數(shù)組原子操作類
AtomicIntegerArray, AtomicLongArray等等.
5, 大幅度提升系統(tǒng)吞吐量及性能.
?
具體使用, 詳解java doc.
轉(zhuǎn)載于:https://www.cnblogs.com/balaamwe/archive/2011/11/22/2259081.html
總結(jié)
以上是生活随笔為你收集整理的volatile原理与技巧的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 鱼相忘于江湖,人相忘于道术
- 下一篇: 回发或回调参数无效。下拉菜单中使用aja