Java并發(fā)問題主要有三個(gè)核心概念:原子性,可見性,順序性。
原子性
并發(fā)問題的原子性的概念和數(shù)據(jù)庫事務(wù)的原子性是一樣的:一個(gè)操作的多個(gè)步驟要么一起生效,要么一起回滾。
java自帶的并發(fā)控制的手段中,可以保障原子性的有:
- synchronized 關(guān)鍵字
- CAS
可見性
可見性的問題是指當(dāng)變量被多個(gè)線程使用時(shí),一個(gè)線程對其作出的修改操作,是否后續(xù)其他線程讀的時(shí)候能馬上讀到修改后的值??梢娦詥栴}的產(chǎn)生是因?yàn)镃PU為了加速數(shù)據(jù)讀取讀取的速度,采用了多級的告訴緩存。
java中保障可見性的手段有
- synchronized關(guān)鍵字
- volidate關(guān)鍵字
順序性
順序指的是代碼的執(zhí)行順序,順序性指的是java編譯器和CPU,在保障單個(gè)線程執(zhí)行的時(shí)代碼的邏輯結(jié)果不變的前提下,可能會(huì)對代碼執(zhí)行順序的作出的一些改變(優(yōu)化)。代碼塊在單線程執(zhí)行的情況下,不管順序怎么變,jvm一定會(huì)保證其邏輯不變,但是多個(gè)線程的情況下如果發(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。但是如果是多個(gè)線程,線程1執(zhí)行方法threadOne,線程2執(zhí)行方法threadTwo,即使不考慮可見性的問題,因?yàn)閠hreadOne的執(zhí)行順序被排序,所以b的值得先被賦值為true,然后線程2讀到了b=true和a=false,最后返回的結(jié)果將是true。
那么怎么解決多線程情況下,順序性帶來的不確定問題呢。一種方法是在編碼時(shí)顯示使用synchronized關(guān)鍵,做到同一時(shí)間只有1個(gè)線程能操作共享的數(shù)據(jù)。另外一個(gè)方法運(yùn)用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ī)則同時(shí)對可見性和順序產(chǎn)生的了影響。happens-before這個(gè)名稱感覺還是挺有歧義的,從"before"這個(gè)字眼上看,以為它定義的是順序性,其實(shí)"before"描述的是可見性的"before"。而順序性,并不能簡單的從字面描述去理解。比如,java規(guī)范定義了多條滿足的happens-before規(guī)則,有一條是:
A write to a volatile field happens-before every subsequent read of that volatile.
它的"before"的定義,不能簡單理解為對一個(gè)volatile變量的寫操作發(fā)生在對其讀操作之前。一個(gè)線程寫,一個(gè)線程讀,jvm可不知道哪個(gè)會(huì)先執(zhí)行到。這里的"before"是可見性的before,也就是,一個(gè)volatile變量的寫操作以及其后面的操作,對另外一個(gè)線程的發(fā)生在這之后的volatile變量的讀操作以及之后的操作可見。
而這條規(guī)則,對順序性的真正的影響是:
- 禁止把volatile寫之前的行為與它重排序
- 禁止把volatile讀之后的行為與它重排序
對于上面的示例程序,只要把變臉b用volatile關(guān)鍵字修飾,那threadOne方法就不會(huì)發(fā)生重排序了。也就是說happens-before規(guī)則也規(guī)定哪些類型的操作之間,編譯器和CPU不能對其進(jìn)行重排序,編程的時(shí)候只要利用這些內(nèi)置的規(guī)則,就能消除重排序在多線程環(huán)境下的問題。
總結(jié)來說,順序性是java為了單線程執(zhí)行的時(shí)候做的優(yōu)化,但是它會(huì)在多線程環(huán)境下帶來問題,并且這些問題基本是不能在編碼階段做理論分析的。為了解決這個(gè)問題,java又自己內(nèi)置了一些規(guī)則(happens-before規(guī)則)對重排序做了一些限制,多線程編程的時(shí)候,只要利用這些規(guī)則,確保我們關(guān)心的關(guān)鍵點(diǎn)不會(huì)發(fā)生重排序,也就保證了程序的正確性。
Lock接口
我們知道Lock是jdk提供的并發(fā)控制工具包,使用Lock也能保證java并發(fā)的三個(gè)重要特性的。但是需要說的Lock只是一個(gè)工具包,它提供的保障都是依賴jvm提供的更底層的機(jī)制來保障的
- 可見性: 是對實(shí)現(xiàn)中的鎖變量使用volidate關(guān)鍵字,然后依賴volidate帶來的happen-before特性,保障加鎖和解鎖之間的修改對其他線程可見的
- 原子性:Lock本身的加鎖解鎖這兩個(gè)行為的原子性是通過CAS操作保障的,而加鎖之后的行為的原子性,是通過控制同一時(shí)間只能由1個(gè)線程執(zhí)行,邏輯保障的
- 順序性: 同原子性,也是通過控制同一時(shí)間只能由1個(gè)線程執(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