java并發(fā)編程要點(diǎn)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容