Java并發(fā)問題主要有三個核心概念:原子性,可見性梅屉,順序性值纱。
原子性
并發(fā)問題的原子性的概念和數(shù)據(jù)庫事務(wù)的原子性是一樣的:一個操作的多個步驟要么一起生效,要么一起回滾坯汤。
java自帶的并發(fā)控制的手段中虐唠,可以保障原子性的有:
- synchronized 關(guān)鍵字
- CAS
可見性
可見性的問題是指當變量被多個線程使用時,一個線程對其作出的修改操作惰聂,是否后續(xù)其他線程讀的時候能馬上讀到修改后的值疆偿。可見性問題的產(chǎn)生是因為CPU為了加速數(shù)據(jù)讀取讀取的速度庶近,采用了多級的告訴緩存翁脆。
java中保障可見性的手段有
- synchronized關(guān)鍵字
- volidate關(guān)鍵字
順序性
順序指的是代碼的執(zhí)行順序眷蚓,順序性指的是java編譯器和CPU鼻种,在保障單個線程執(zhí)行的時代碼的邏輯結(jié)果不變的前提下,可能會對代碼執(zhí)行順序的作出的一些改變(優(yōu)化)沙热。代碼塊在單線程執(zhí)行的情況下叉钥,不管順序怎么變,jvm一定會保證其邏輯不變篙贸,但是多個線程的情況下如果發(fā)生了重排序投队,則局部的邏輯可能是沒變,單是全局的邏輯已經(jīng)有問題了爵川,比如下面的代碼塊
class BadlyOrdered {
boolean a = false;
boolean b = false;
void threadOne() {
a = true;
b = true;
}
boolean threadTwo() {
boolean r1 = b; // sees true
boolean r2 = a; // sees false
return r1 && !r2; // returns true
}
}
方法threadOne在單線程執(zhí)行的情況下敷鸦,不管順序怎么變,其最終的結(jié)果都是a和b都賦值為true寝贡。但是如果是多個線程扒披,線程1執(zhí)行方法threadOne,線程2執(zhí)行方法threadTwo圃泡,即使不考慮可見性的問題碟案,因為threadOne的執(zhí)行順序被排序,所以b的值得先被賦值為true颇蜡,然后線程2讀到了b=true和a=false价说,最后返回的結(jié)果將是true。
那么怎么解決多線程情況下风秤,順序性帶來的不確定問題呢鳖目。一種方法是在編碼時顯示使用synchronized關(guān)鍵,做到同一時間只有1個線程能操作共享的數(shù)據(jù)缤弦。另外一個方法運用jvm自帶的happens-before規(guī)則疑苔。
happens-before
happens-before規(guī)則的描述如下
Happens-Before Relationship : Two actions can be ordered by a happens-before relationship.If one action happens before another, then the first is visible to and ordered before the second.
可見,happens-before規(guī)則同時對可見性和順序產(chǎn)生的了影響。happens-before這個名稱感覺還是挺有歧義的惦费,從"before"這個字眼上看兵迅,以為它定義的是順序性,其實"before"描述的是可見性的"before"薪贫。而順序性恍箭,并不能簡單的從字面描述去理解。比如瞧省,java規(guī)范定義了多條滿足的happens-before規(guī)則扯夭,有一條是:
A write to a volatile field happens-before every subsequent read of that volatile.
它的"before"的定義,不能簡單理解為對一個volatile變量的寫操作發(fā)生在對其讀操作之前鞍匾。一個線程寫交洗,一個線程讀,jvm可不知道哪個會先執(zhí)行到橡淑。這里的"before"是可見性的before构拳,也就是,一個volatile變量的寫操作以及其后面的操作梁棠,對另外一個線程的發(fā)生在這之后的volatile變量的讀操作以及之后的操作可見置森。
而這條規(guī)則,對順序性的真正的影響是:
- 禁止把volatile寫之前的行為與它重排序
- 禁止把volatile讀之后的行為與它重排序
對于上面的示例程序符糊,只要把變臉b用volatile關(guān)鍵字修飾凫海,那threadOne方法就不會發(fā)生重排序了。也就是說happens-before規(guī)則也規(guī)定哪些類型的操作之間男娄,編譯器和CPU不能對其進行重排序行贪,編程的時候只要利用這些內(nèi)置的規(guī)則,就能消除重排序在多線程環(huán)境下的問題模闲。
總結(jié)來說建瘫,順序性是java為了單線程執(zhí)行的時候做的優(yōu)化,但是它會在多線程環(huán)境下帶來問題围橡,并且這些問題基本是不能在編碼階段做理論分析的暖混。為了解決這個問題,java又自己內(nèi)置了一些規(guī)則(happens-before規(guī)則)對重排序做了一些限制翁授,多線程編程的時候拣播,只要利用這些規(guī)則,確保我們關(guān)心的關(guān)鍵點不會發(fā)生重排序收擦,也就保證了程序的正確性贮配。
Lock接口
我們知道Lock是jdk提供的并發(fā)控制工具包,使用Lock也能保證java并發(fā)的三個重要特性的塞赂。但是需要說的Lock只是一個工具包泪勒,它提供的保障都是依賴jvm提供的更底層的機制來保障的
- 可見性: 是對實現(xiàn)中的鎖變量使用volidate關(guān)鍵字,然后依賴volidate帶來的happen-before特性,保障加鎖和解鎖之間的修改對其他線程可見的
- 原子性:Lock本身的加鎖解鎖這兩個行為的原子性是通過CAS操作保障的圆存,而加鎖之后的行為的原子性叼旋,是通過控制同一時間只能由1個線程執(zhí)行,邏輯保障的
- 順序性: 同原子性沦辙,也是通過控制同一時間只能由1個線程執(zhí)行的保障的
總結(jié)
原子性 | 可見性 | 順序性 | |
---|---|---|---|
sychronized | 保證 | 保證 | 保證 |
volatile | 不保證 | 保證 | 部分保證 |
CAS操作 | 保證 | 不保證 | 不保證 |
Lock工具類 | 保證 | 保證 | 保證 |
####### refer
http://www.infoq.com/cn/articles/java-memory-model-2
http://ifeve.com/easy-happens-before/
http://blog.csdn.net/admiral_dota/article/details/50489882