1. 程式人生 > >八、JVM視角淺理解並發和鎖

八、JVM視角淺理解並發和鎖

之前 鎖定 線程 並發 標示 靜態變量 原子操作 store 執行順序

  根據《深入理解java虛擬機》這本書總結

  提到java的並發和鎖,第一反應可能回想到多線程、synchronized關鍵字等,那麽對於jvm虛擬機,這些是如何實現的呢?或者用的什麽思想實現的?

  一、JAVA內存模型

    為什麽要談到內存模型?並發編程和鎖要解決的問題就是同步的問題,拋開java代碼,虛擬機自身是如何實現單線程甚至是多線程保證同步的。這就需要對內存模型又了解,虛擬機如何讀取、修改、保存數據,在多線程的情況下,又是如何使這些操作安全,這就是了解內存模型的出發點。

    內存模型?jvm運行時內存?這裏的java內存模型跟jvm內存時的區域劃分個人感覺也是有一些聯系的。java內存模型主要分:主內存和工作內存。其中主內存屬於線程共享的區域,可以跟運行內存的堆和方法去對應,工作內存則是線程運行時的私有內存,可以跟運行時內存的棧對應起來。

    主內存?主內存是所有線程共享的,存儲一些變量(不包括方法的局部變量和方法的入參數,因為這些是線程內部私有的並不會涉及到並發問題,只包括成員變量、實例變量、靜態變量等),線程不可以直接對主內存中的數據進行讀寫的操作。

    工作內存?工作內存是線程私有的,當線程需要使用一個變量時,不能直接讀寫主內存的變量,而是給線程自身設置了一個專屬的工作內存,當前線程會將需要用的變量read到工作線程,然後通過load操作在工作內存中放入主內存的變量副本。線程只能對這些變量副本進行操作,操作完畢後,需要store該變量到主內存中,然後通過write操作將變量的值放入變量中。

  二、主內存和工作內存的交互操作

    上面粗略的講了主內存和工作內存對變量的操作。那麽這些操作中都涉及到哪些交互動作呢?

    1、lock:作用於主內存變量,將一個變量標示為一個線程獨占的狀態。

    2、unlock:將lock操作後鎖定狀態的變量釋放出來,只有unlock後變量才能被其他線程鎖定。

    3、read:讀取主內存變量,並傳輸到工作內存,供load操作載入  

    4、load:載入read讀取到的數據,將主內存傳入的數據,放入工作內存的變量副本中。

    5、use:在工作內存中使用該變量,將該變量傳給執行引擎

    6、assign:工作內存操作變量的值

    7、store:將工作內存的變量傳輸給主內存

    8、write:將store傳輸的變量,寫入主內存對應的變量中

    在這些操作中,虛擬機指定了一些規則,如3456操作不能單獨出現,不允許工作線程未進行6操作的情況下執行78操作,在對對象進行2操作之前,必須已經執行78操作等等。另外也有一些特殊情況,如被volatile修飾的變量,在工作線程中修改,其余線程也能察覺到。又比如對於long和double的變量在64位系統中,可以將讀寫操作分為兩次32位的操作進行等等。這些就不具體理解..

    上面的操作都是原子操作(特殊情況如long、double除外),當兩個線程同時運行時,在沒有進行lock的情況下,如果同時read變量i值為1,放入工作內存,並且在工作內存中分別+1,在最後78操作後,最終值為2,但是兩個線程分別+1,結果應該為3,這就是多線程的情況下沒有同步導致的問題。下面會闡述,java提供了哪些規則/思想,在單線程和多線程來保證數據的同步。

  三、單線程的代碼執行

    我們寫一段代碼,對於當前線程來說代碼是按照一行一行,更精確的講是按照邏輯一步一步運行的,脫離這個線程,站在其他線程的的角度看,代碼確不是一步一步的執行的,有可能後面的邏輯先行執行,執行順序是沒有保證的,但是java是在經過計算,保證在結果一致的情況下去執行指令,所以對於單線程,我們是完全感知不到的。

  四、多線程的同步

    1、互斥同步:多線程並發訪問共享數據,保證共享數據在同一時刻只有一個線程能訪問,最基本的互斥同步就是synchronize關鍵字,經過代碼編譯後,在需要同步的前後分別形成monitorenter和monitorexit兩個字節碼指令,這兩個字節碼都需要對於的現場來鎖定和解鎖。另外synchronize對於同一個線程是可以重入的,如果當前線程再次執行同步方法,在原先獲取鎖的計數上加1,當然解鎖的時候-1,只有減到0的時候,才能真正的釋放鎖。

     juc包中lock下reentrantlock也是一種互斥同步鎖,這種鎖比synchronize更加輕量級,並且更加靈活,synchronize修飾,獲取不到鎖的時候,需要將線程掛起,這就需要切換到操作系統內核去執行,開銷也是很大的,並且reentrantlock提供了很多條件,如果獲取不到鎖,可以進行別的動作。

    2、非堵塞同步:上面說到的互斥同步,主要的缺點就是線程進行堵塞和喚醒導致性能問題,因此在處理方式上來說,互斥同步鎖可以理解成一種悲觀的並發策略,因為他每次都考慮會有多個線程對資源進行競爭,必須要在獲得鎖的情況下才去操作。而這邊的非堵塞同步鎖,則可以理解成是一種樂觀引發策略。就是先進行操作,如果沒有其他線程對資源競爭,則操作成功,否則操作失敗,當然這裏也要有標誌條件去標記有沒有其余線程的競爭操作。這裏用到了cas的操作,比較當前值是否是預想的值,如果是,設置為新值,否則失敗。

    3、另外還有幾個概念:自旋鎖(獲取不到鎖的情況,不進行掛起,線程自旋,在一定時間內還獲取不到鎖再掛起)、自適應自旋鎖(根據之前獲取該鎖通過自旋方式的成功率,自適應是否要進行自旋操作)。

  

        

八、JVM視角淺理解並發和鎖