乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      讀書筆記之Java并發(fā)原理與JMM

       黃家v少 2018-04-12

      備注:本文是《Java并發(fā)編程的藝術》一書的讀書筆記。

      1
      Java并發(fā)機制的底層原理實現(xiàn)

      volatile的應用

       volatile是輕量級的synchronized,他在多處理器開發(fā)中保證了共享變量的“可見性”??梢娦缘囊馑际钱斠粋€線程修改一個共享變量時,另外一個線程能讀到這個修改的值。如果volatile變量修飾符使用恰當?shù)脑?,它比synchronized的使用和執(zhí)行成本更低,因為它不會引起線程上下文的切換和調(diào)度。


      synchronized的實現(xiàn)原理與應用

       synchronized實現(xiàn)同步的基礎:Java中的每一個對象都可以作為鎖。具體表現(xiàn)為以下3中形勢* 

       1.對于普通同步方法,鎖是當前實例對象。
       2.對于靜態(tài)同步方法,鎖是當前類的Class對象。
       3.對于同步方法塊,鎖是Synchronized括號里配置的對象。
       當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,推出或拋出異常時必須釋放鎖。  

      偏向鎖

        當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖。如果測試成功,表示線程已經(jīng)獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。偏向鎖使用了一種等到競爭出現(xiàn)才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有正在執(zhí)行的字節(jié)碼)。  

      輕量級鎖

        線程在執(zhí)行同步塊之前,JVM會先在當前線程的棧幀中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word 替換回到對象頭,如果成功,則表示沒有競爭發(fā)生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦確認升級成重量級鎖,就不會再恢復到輕量級鎖狀態(tài)。當鎖處于這個狀態(tài)下,其他線程試圖獲取鎖時,都會被阻塞住,當持有鎖的線程釋放鎖之后會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭。  

      鎖的優(yōu)缺點對比

                                                                                                                                                                             

      優(yōu)點缺點適用場景
      偏向鎖加鎖和解鎖不需要額外的消耗,和執(zhí)行非同步方法相比僅存在納秒級的差距如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗適用于只有一個線程訪問同步塊場景
      輕量級鎖競爭的線程不會阻塞,提高了線程的響應速度如果始終得不到鎖競爭的線程,使用自旋會消耗CPU追求響應時間
      同步塊執(zhí)行速度非???/td>
      重量級鎖線程競爭不適用自旋,不會消耗CPU線程阻塞,響應時間緩慢追求吞吐量
      同步塊執(zhí)行速度較長

      原子操作的實現(xiàn)原理

       

      原子(atomic)本意是“不能被進一步分割的最小粒子“,而原子操作(atomic operation)意為“不可被中斷的一個或一系列操作“。  

      術語定義

                                                                                                                                                                     

      術語名稱英文解釋
      緩存行Cache line緩存的最小操作單位
      比較并交換Compare and SwapCAS操作需要輸入兩個數(shù)值,一個舊值(期望操作前的值)和一個新值,在操作期間先比較舊值有沒有發(fā)生變化,如果沒有發(fā)生變化,才交換成新值,發(fā)生了變化則不交換
      CPU流水線CPU pipelineCPU流水線的工作方式就像工業(yè)生產(chǎn)上的裝配流水線,在CPU中由5-6個不同功能的電路單元組成一條指令處理流水線,然后將一條X86指令分成5-6步后再由這點電路單元分別執(zhí)行,這樣就能實現(xiàn)在一個CPU時鐘周期完成一條指令,因此提高CPU的運算速度
      內(nèi)存順序沖突Memory order violation內(nèi)存順序沖突一般是由假共享引起的,假共享是指多個CPU同時修改同一個緩存行的不同部分而引起其中一個CPU的操作無效,當出現(xiàn)這個內(nèi)存順序沖突時,CPU必須清空流水線

      處理器如何實現(xiàn)原子操作

       處理器提供總線鎖定和緩存鎖定兩個機制來保證復雜內(nèi)存操作的原子性

      (1)使用總線鎖保證原子性:第一機制是通過總線鎖保證原子性。保證CPU1讀改寫共享變量的時候,CPU2不能操作緩存了該共享變量內(nèi)存地址的緩存。所謂總線鎖就是使用處理器提供的一個LOCK#信號,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞住,那么該處理器可以獨享共享內(nèi)存。
      (2)使用緩存鎖保證原子性:第二個機制是通過緩存鎖定來保證原子性。在同一時刻,我們只需保證對某個內(nèi)存地址的操作是原子性即可,但總線鎖定把CPU和內(nèi)存之間的通信鎖住了,這使得鎖定期間,其他處理器不能操作其它內(nèi)存地址的數(shù)據(jù),所以總線鎖定的開銷比較大,目前處理器在某些場景下使用緩存鎖定代替總線鎖定來進行優(yōu)化。所謂“緩存鎖定”是指內(nèi)存區(qū)域如果被緩存在處理器的緩存行中,并且在Lock操作期間被鎖定,那么當其他執(zhí)行鎖操作回寫到內(nèi)存時,處理器不在總線上聲言LOCK#信號,而是修改內(nèi)部的內(nèi)存地址,并允許它的緩存一致性機制來保證操作的原子性,因為緩存一致性機制會阻止同時修改由兩個以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù),到其他處理器回寫已被鎖定的緩存行數(shù)據(jù)時,會使緩存行無效。
      但是有兩種情況下處理器不回使用緩存鎖定:當操作的數(shù)據(jù)不能被緩存在處理器內(nèi)部,或操作的數(shù)據(jù)跨多個緩存行時,則處理器會調(diào)用總線鎖定。當有些處理器不支持緩存鎖定。

      2
      Java如何實現(xiàn)原子操作


      (1)使用循環(huán)CAS實現(xiàn)原子操作:JVM中的CAS操作正是利用了處理器提供的CMPXCHG指令實現(xiàn)的。自旋CAS實現(xiàn)的基本思路就是循環(huán)進行CAS操作直到成功為止。從Java1.5開始,JDK的并發(fā)包里提供了一些類來支持原子操作,如AtomicBoolean。
      (2)CAS實現(xiàn)原子操作的三大問題ABA問題,因為CAS需要在操作值的時候,檢查值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那么使用CAS進行檢查時會發(fā)現(xiàn)它的值沒有變化,但是實際上卻變化了。ABA問題的解決思路是使用版本號,在變量前追加版本號,每次變量更新的時候把版本號加1,那么A-B-A就會變成了1A-2B-3A。從JAVA1.5開始,JDK的Atomic包里提供了一個類AtomicStampedreference來解決ABA問題。這個類的compareAndSet方法的作用是首先檢查當前引用是否等于預期引用,并且檢查當前標志是否等于預期標志,如果全部相等,則以原子方式將該引用和該標識的值設置為給定的更新值。循環(huán)時間長開銷大,自旋CAS如果長時間不成功,會給CPU帶來非常大的執(zhí)行開銷。如果JVM能支持處理器提供的pause指令,那么效率會有一定的提升。pause指令有兩個作用:第一,它可以延遲流水線執(zhí)行指令,使CPU不會消耗過多的執(zhí)行資源,延遲的時間取決于具體的版本,在一些處理器上延遲時間是零;第二,它可以避免在退出循環(huán)的時候因內(nèi)存順序沖突而引起CPU流水線被清空,從而提高CPU的執(zhí)行效率。只能保證一個共享變量的原子性,當對一個共享變量執(zhí)行操作時,我們可以使用循環(huán)CAS的方式來保證原子操作,但是對多個共享變量操作時,循環(huán)CAS就無法保證操作的原子性,這個時候就可以用鎖。還有一個取巧的辦法,就是把多個共享變量合并成一個共享變量來操作。Java1.5以后JDK提供了AtomicReference類來保證引用對象之間的原子性,就可以把多個變量放在一個對象里來進行CAS操作。
      (3)使用鎖機制實現(xiàn)原子操作:鎖機制保證了只有獲得鎖的線程才能狗操作鎖定的內(nèi)存區(qū)域。JVM內(nèi)部實現(xiàn)了很多種鎖機制,有偏向鎖、輕量級鎖和互斥鎖。有意思的是除了偏向鎖,JVM實現(xiàn)鎖的方式都用了循環(huán)CAS,即一個線程想進入同步塊的時候使用循環(huán)CAS的方式來獲取鎖,當它退出同步塊的時候使用循環(huán)CAS釋放鎖。

      3
      Java內(nèi)存模型

      Java內(nèi)存模型的基礎

       在并發(fā)編程中,需要處理兩個關鍵問題:線程之間如何通信及線程之間如何同步(這里的線程是指并發(fā)執(zhí)行的活動實體)。通行是指線程之間以何種機制來交換信息。在命令式編程中,線程之間的通信機制有兩種:內(nèi)存共享消息傳遞。
         

      同步 是指程序中用于控制不同線程間操作發(fā)生相對順序的機制。在共享內(nèi)存并發(fā)模型里,同步是顯性進行的。程序員需要顯性執(zhí)行某個方法或某段代碼需要在線程之間互斥執(zhí)行。在消息傳遞的并發(fā)模型里,由于消息的發(fā)送必須在消息的接受之前,因此同步是隱形進行的。

      Java并發(fā)采用的是共享內(nèi)存模型。

      JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互,來為Java程序員提供內(nèi)存可見性。

      從源代碼到指令序列的重排序

      在執(zhí)行程序時,為了提高性能,編譯器和處理器常常會對指令做重排序,重排序分3種類型。
      1)編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。
      2)指令級并行的重排序?,F(xiàn)代處理器采用了指令級并行技術來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應機器指令的執(zhí)行順序。
      3)內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行。

      happens-before簡介

       JSR-133(JDK1.5開始Java使用的新的內(nèi)存模型)使用 happens-before 的概念來闡述操作之間的內(nèi)存可見性。在JMM中,如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見,那么這兩個操作之間必須要存在happens-before關系。這里提到的兩個操作既可以是在一個線程內(nèi),也可以是在不同線程之間。
         

      程序順序規(guī)則:一個線程中的每個操作,happens-before于該線程中的任意后續(xù)操作。
       監(jiān)視器鎖規(guī)則:對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖。
       volatile變量規(guī)則:對一個volatile域的寫,happens-before于任意后續(xù)對這個 volatile域的讀。
       傳遞性:如果A heppens-before于B,且B happnes-before C, 那么A happens-before C。

      兩個操作之間具有happens-before關系,并不意味著前一個操作必須要在后一個操作之前執(zhí)行,happens-before僅僅要求前一個操作(執(zhí)行的結(jié)果)對后一個操作可見,且前一個操作按順序排在第二個操作之前。

      重排序

       重排序是指編譯器和處理器為了優(yōu)化程序性能而對指令序列進行重新排序的一種手段。

      as-if-serial語義

       as-if-serial的語義是:不管怎么重排序,單線程程序執(zhí)行的結(jié)果不能被改變。編譯器、runtime和處理器都必須遵守as-if-serial語義。

      volatile的內(nèi)存語義

      • 可見性。對一個volatile變量的讀,總是能看到任意線程對這個volatile變量最后的寫入。

      • 原子性。對任意單個volatile變量的讀/寫具有原子性,但類似于volatile++這種復合操作不具有原子性。


      volatile寫-讀的內(nèi)存語義:當寫一個volatile變量時,JMM會把該線程對應的本地內(nèi)存中的共享變量值刷新到主內(nèi)存。
      volatile讀的內(nèi)存語義:但讀一個volatile變量時,JMM會把該線程對應的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量。
      為了實現(xiàn)volatile的內(nèi)存語義,編譯器在生成字節(jié)碼時,會在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序。

      鎖的內(nèi)存語義

      鎖的釋放和獲取的內(nèi)存語義

      • 當線程釋放鎖時:JMM會把該線程對應的本地內(nèi)存中的共享變量刷新到主內(nèi)存中。  

      • 當線程獲取鎖時:JMM會把該線程對應的本地變量置為無效,從而使得被監(jiān)視器保護的臨界區(qū)代碼必須從主內(nèi)存中讀取共享變量。


      CAS具有volatile讀和volatile寫的內(nèi)存語義

      公平鎖與非公平鎖的內(nèi)存語義(ReentrantLock為例):

      • 公平鎖和非公平鎖釋放時,最后都要寫一個volatile變量state。  

      • 公平鎖獲取時,首先會去讀volatile變量。  

      • 非公平鎖獲取時,首先會用CAS更新volatile變量,這個操作同時具有volatile讀和volatile寫的內(nèi)存語義。


      從對ReentrantLock的分析可以看出,釋放鎖-獲取鎖的內(nèi)存語義的實現(xiàn)至少有下面兩種方式:

      • 利用volatile變量的寫-讀所具有的內(nèi)存語義。

      • 利用CAS所附帶的volatile讀和volatile寫的內(nèi)存語義。  


      final域的內(nèi)存語義

      • 在構造函數(shù)內(nèi)對一個final域的寫入,與隨后把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。


      • 初次讀一個包含final域的對象的引用,與隨后初次讀這個final域,這兩個操作之間不能重排序。


      JSR-133為什么要增強final的語義?
      在舊的Java內(nèi)存模型中,一個最嚴重的缺陷就是線程可能看到final域的值會改變,比如,一個線程當前看到一個證書final域的值為0(還未初始化之前的默認值),過一段時間之后這個線程再去讀這個final域的值時,卻發(fā)現(xiàn)值變成了1(被某個線程初始化之后的值)。

      happens-before

      happens-before定義:  

      • 如果一個操作happens-before另一個操作,那么第一個操作的執(zhí)行結(jié)果將對第二個操作可見,而且第一個操作的執(zhí)行順序排在第二個操作之前。(是JMM對程序員的承諾)

      • 兩個操作之間存在happens-before關系,并不意味著Java平臺的具體實現(xiàn)必須要按照happens-before關系執(zhí)行的順序來執(zhí)行。如果重排序之后的執(zhí)行結(jié)果,與按happens-before關系來執(zhí)行的結(jié)果一直,那么這種重排序是合法的。(是JMM對編譯器和處理器重排序的約束原則)


       as-if-serial語義保證單線程內(nèi)程序的執(zhí)行結(jié)果不被改變,happens-before關系保證正確同步的多線程程序的執(zhí)行結(jié)果不被改變。 

       as-if-serial語義給編寫單線程程序的程序員創(chuàng)造了一個幻境:單線程程序是按程序的順序來執(zhí)行的。happens-before關系給編寫正確同步的多線程的程序員創(chuàng)造了一個幻境:正確同步的多線程程程是按happens-before指定的順序來執(zhí)行的。

      happens-before規(guī)則:

      • 程序順序規(guī)則:一個線程中的每個操作,happens-before于該線程中的任意后續(xù)操作。

      • 監(jiān)視器鎖規(guī)則:對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖。

      • volatile變量規(guī)則:對一個volatile域的寫,happens-before于任意后續(xù)對這個volatile域的讀。

      • 傳遞性:如果A happens-before B, 且B happens-before C, 那么A happens-before C。

      • start()規(guī)則:如果線程A執(zhí)行操作ThreadB.start(),那么A線程的ThreadB.start()操作happens-before于線程B中的任意操作。

      • join()規(guī)則:如果線程A執(zhí)行操作ThreadB.join()并成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。


      雙重檢查鎖定與延遲初始化

      public class DoubleCheckedLocking {
         private static Instance instance;

         public static Instance getInsatnce() {
             if (instance == null) {
                 synchronized(DoubleCheckedLocking.class) {
                     if (instance == null) {
                         instance = new DoubleCheckedLocking();
                     }
                 }
             }
         }
      }
      • 雙重檢查是比較常見的一種延遲初始化方案,但是還是會存在一些問題:

      memory = allocate();    
      ctorInstance(memory);  
      instance = memory;


      上面?zhèn)未a中2和3可能會被重排序,這種情況下返回的instance引用可能還沒有初始化完成。這個時候我們要從兩個方面解決問題:
      1.不允許2和3重排序。
      2.允許2和3重排序,但不允許其他線程“看到”這個重排序。

      基于volatile的解決方案

      public class DoubleCheckedLocking {
         private volatile static Instance instance;

         public static Instance getInsatnce() {
             if (instance == null) {
                 synchronized(DoubleCheckedLocking.class) {
                     if (instance == null) {
                         instance = new DoubleCheckedLocking();
                         
                     }
                 }
             }
         }
      }
      • 基于類初始化的解決方案

      public class DoubleCheckedLocking {
         private static class InstanceHolder {
             public static Instance instance = new Instance();
         }

         public static Instance getInsatnce() {
             return InstanceHolder.instance;
         }
      }

        本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多